Merge remote-tracking branch 'goog/ics-aah'
diff --git a/Android.mk b/Android.mk
index 8f1acc5..91b2fbb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -12,6 +12,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-subdir-makefiles)
diff --git a/dictionaries/sample.xml b/dictionaries/sample.xml
index 85233b6..ad98f2b 100644
--- a/dictionaries/sample.xml
+++ b/dictionaries/sample.xml
@@ -2,7 +2,9 @@
      for use by the Latin IME.
      The format of the word list is a flat list of word entries.
      Each entry has a frequency between 255 and 0.
-     Highest frequency words get more weight in the prediction algorithm.
+     Highest frequency words get more weight in the prediction algorithm. As a
+     special case, a weight of 0 is taken to mean profanity - words that should
+     not be considered a typo, but that should never be suggested explicitly.
      You can capitalize words that must always be capitalized, such as "January".
      You can have a capitalized and a non-capitalized word as separate entries,
      such as "robin" and "Robin".
@@ -13,4 +15,3 @@
   <w f="128">sample</w>
   <w f="1">wordlist</w>
 </wordlist>
-
diff --git a/java/Android.mk b/java/Android.mk
index 43168e5..52cc18b 100644
--- a/java/Android.mk
+++ b/java/Android.mk
@@ -1,3 +1,17 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -9,10 +23,14 @@
 
 LOCAL_CERTIFICATE := shared
 
+# We want to package libjni_latinime.so into the apk.
 LOCAL_JNI_SHARED_LIBRARIES := libjni_latinime
+# We want to install libjni_latinime.so to the system partition if LatinIME gets installed.
+LOCAL_REQUIRED_MODULES := libjni_latinime
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-common
 LOCAL_STATIC_JAVA_LIBRARIES += inputmethod-common
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
 
 # Do not compress dictionary files to mmap dict data runtime
 LOCAL_AAPT_FLAGS := -0 .dict
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index b052532..9f62e97 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -5,16 +5,15 @@
     <uses-permission android:name="android.permission.VIBRATE"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
-    <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
 
-    <application android:label="@string/english_ime_name"
+    <application android:label="@string/aosp_android_keyboard_ime_name"
             android:icon="@drawable/ic_ime_settings"
             android:backupAgent="BackupAgent"
             android:killAfterRestore="false">
 
         <service android:name="LatinIME"
-                android:label="@string/english_ime_name"
+                android:label="@string/aosp_android_keyboard_ime_name"
                 android:permission="android.permission.BIND_INPUT_METHOD">
             <intent-filter>
                 <action android:name="android.view.InputMethod" />
@@ -50,15 +49,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.inputmethod.deprecated.languageswitcher.InputLanguageSelection"
-                android:label="@string/language_selection_title">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <action android:name="com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"/>
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <receiver android:name="SuggestionSpanPickedNotificationReceiver" android:enabled="true">
             <intent-filter>
                 <action android:name="android.text.style.SUGGESTION_PICKED" />
diff --git a/java/proguard.flags b/java/proguard.flags
index 33af890..2ee1a26 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -1,6 +1,10 @@
 -keep class com.android.inputmethod.latin.BinaryDictionary {
   int mDictLength;
-  <init>(...); 
+  <init>(...);
+}
+
+-keep class com.android.inputmethod.latin.Flag {
+  *;
 }
 
 -keep class com.android.inputmethod.latin.Suggest {
@@ -8,13 +12,6 @@
   com.android.inputmethod.latin.SuggestedWords getSuggestions(...);
 }
 
--keep class com.android.inputmethod.latin.UserBigramDictionary {
-  void setDatabaseMax(int);
-  void setDatabaseDelete(int);
-  void waitUntilUpdateDBDone();
-  void waitForDictionaryLoading();
-}
-
 -keep class com.android.inputmethod.latin.AutoCorrection {
   java.lang.CharSequence getAutoCorrectionWord();
 }
@@ -23,18 +20,32 @@
   boolean equalsIgnoreCase(...);
 }
 
--keep class com.android.inputmethod.latin.spellcheck.SpellChecker {
-  *;
-}
-
 -keep class com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment {
   *;
 }
 
--keep class com.android.inputmethod.latin.SettingsActivity {
-  *;
+-keep class com.android.inputmethod.keyboard.LatinKeyboardView {
+  # Keep getter/setter methods for ObjectAnimator
+  int getLanguageOnSpacebarAnimAlpha();
+  void setLanguageOnSpacebarAnimAlpha(int);
+  int getAltCodeKeyWhileTypingAnimAlhpa();
+  void setAltCodeKeyWhileTypingAnimAlpha(int);
 }
 
--keep class com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder$MiniKeyboardParams {
+-keep class com.android.inputmethod.keyboard.MoreKeysKeyboard$Builder$MoreKeysKeyboardParams {
   <init>(...);
 }
+
+-keep class com.android.inputmethod.latin.ResearchLogger {
+  void setLogFileManager(...);
+}
+
+-keep class com.android.inputmethod.keyboard.KeyboardSet$Builder {
+  void setTouchPositionCorrectionEnabled(...);
+}
+
+# The support library contains references to newer platform versions.
+# Don't warn about those in case this app is linking against an older
+# platform version.  We know about them, and they are safe.
+-dontwarn android.support.v4.**
+-dontwarn android.support.v13.**
diff --git a/java/res/anim/mini_keyboard_fadeout.xml b/java/res/anim/alt_code_key_while_typing_fadein.xml
similarity index 70%
copy from java/res/anim/mini_keyboard_fadeout.xml
copy to java/res/anim/alt_code_key_while_typing_fadein.xml
index 535b100..f8caca3 100644
--- a/java/res/anim/mini_keyboard_fadeout.xml
+++ b/java/res/anim/alt_code_key_while_typing_fadein.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,12 +18,10 @@
 */
 -->
 
-<set
+<objectAnimator
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/accelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.0"
-        android:duration="@integer/config_mini_keyboard_fadeout_anim_time" />
-</set>
+    android:propertyName="altCodeKeyWhileTypingAnimAlpha"
+    android:valueType="intType"
+    android:duration="100"
+    android:valueFrom="128"
+    android:valueTo="255" />
diff --git a/java/res/anim/mini_keyboard_fadeout.xml b/java/res/anim/alt_code_key_while_typing_fadeout.xml
similarity index 70%
copy from java/res/anim/mini_keyboard_fadeout.xml
copy to java/res/anim/alt_code_key_while_typing_fadeout.xml
index 535b100..bad1e74 100644
--- a/java/res/anim/mini_keyboard_fadeout.xml
+++ b/java/res/anim/alt_code_key_while_typing_fadeout.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,12 +18,10 @@
 */
 -->
 
-<set
+<objectAnimator
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/accelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.0"
-        android:duration="@integer/config_mini_keyboard_fadeout_anim_time" />
-</set>
+    android:propertyName="altCodeKeyWhileTypingAnimAlpha"
+    android:valueType="intType"
+    android:duration="70"
+    android:valueFrom="255"
+    android:valueTo="128" />
diff --git a/java/res/anim/mini_keyboard_fadeout.xml b/java/res/anim/language_on_spacebar_fadeout.xml
similarity index 68%
copy from java/res/anim/mini_keyboard_fadeout.xml
copy to java/res/anim/language_on_spacebar_fadeout.xml
index 535b100..531f440 100644
--- a/java/res/anim/mini_keyboard_fadeout.xml
+++ b/java/res/anim/language_on_spacebar_fadeout.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,12 +18,11 @@
 */
 -->
 
-<set
+<objectAnimator
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/accelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.0"
-        android:duration="@integer/config_mini_keyboard_fadeout_anim_time" />
-</set>
+    android:propertyName="languageOnSpacebarAnimAlpha"
+    android:valueType="intType"
+    android:startOffset="1200"
+    android:duration="200"
+    android:valueFrom="255"
+    android:valueTo="@integer/config_language_on_spacebar_final_alpha" />
diff --git a/java/res/anim/mini_keyboard_fadein.xml b/java/res/anim/more_keys_keyboard_fadein.xml
similarity index 91%
rename from java/res/anim/mini_keyboard_fadein.xml
rename to java/res/anim/more_keys_keyboard_fadein.xml
index f80e8b8..c781f36 100644
--- a/java/res/anim/mini_keyboard_fadein.xml
+++ b/java/res/anim/more_keys_keyboard_fadein.xml
@@ -25,5 +25,5 @@
     <alpha
         android:fromAlpha="0.5"
         android:toAlpha="1.0"
-        android:duration="@integer/config_mini_keyboard_fadein_anim_time" />
+        android:duration="@integer/config_more_keys_keyboard_fadein_anim_time" />
 </set>
diff --git a/java/res/anim/mini_keyboard_fadeout.xml b/java/res/anim/more_keys_keyboard_fadeout.xml
similarity index 91%
rename from java/res/anim/mini_keyboard_fadeout.xml
rename to java/res/anim/more_keys_keyboard_fadeout.xml
index 535b100..32fae6b 100644
--- a/java/res/anim/mini_keyboard_fadeout.xml
+++ b/java/res/anim/more_keys_keyboard_fadeout.xml
@@ -25,5 +25,5 @@
     <alpha
         android:fromAlpha="1.0"
         android:toAlpha="0.0"
-        android:duration="@integer/config_mini_keyboard_fadeout_anim_time" />
+        android:duration="@integer/config_more_keys_keyboard_fadeout_anim_time" />
 </set>
diff --git a/java/res/drawable-hdpi/caution.png b/java/res/drawable-hdpi/caution.png
deleted file mode 100644
index 61eb4dd..0000000
--- a/java/res/drawable-hdpi/caution.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_mic_dialog.png b/java/res/drawable-hdpi/ic_mic_dialog.png
deleted file mode 100644
index 6107f87..0000000
--- a/java/res/drawable-hdpi/ic_mic_dialog.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_suggest_strip_microphone.png b/java/res/drawable-hdpi/ic_suggest_strip_microphone.png
deleted file mode 100644
index 189a861..0000000
--- a/java/res/drawable-hdpi/ic_suggest_strip_microphone.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png b/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png
deleted file mode 100644
index b4a6e37..0000000
--- a/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/mic_base.png b/java/res/drawable-hdpi/mic_base.png
deleted file mode 100644
index 504a1aa..0000000
--- a/java/res/drawable-hdpi/mic_base.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/mic_full.png b/java/res/drawable-hdpi/mic_full.png
deleted file mode 100644
index 3f4a676..0000000
--- a/java/res/drawable-hdpi/mic_full.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/mic_slash.png b/java/res/drawable-hdpi/mic_slash.png
deleted file mode 100644
index c3b1092..0000000
--- a/java/res/drawable-hdpi/mic_slash.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/more_keys_divider.png b/java/res/drawable-hdpi/more_keys_divider.png
new file mode 100644
index 0000000..a5912f9
--- /dev/null
+++ b/java/res/drawable-hdpi/more_keys_divider.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_language_switch.png b/java/res/drawable-hdpi/sym_keyboard_language_switch.png
new file mode 100644
index 0000000..fa74764
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_language_switch.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-hdpi/sym_keyboard_zwj_holo.png
new file mode 100644
index 0000000..5fa30ce
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_zwj_holo.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-hdpi/sym_keyboard_zwnj_holo.png
new file mode 100644
index 0000000..91367f3
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_zwnj_holo.png
Binary files differ
diff --git a/java/res/drawable-hdpi/vs_dialog_blue.9.png b/java/res/drawable-hdpi/vs_dialog_blue.9.png
deleted file mode 100644
index 4f813ea..0000000
--- a/java/res/drawable-hdpi/vs_dialog_blue.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/vs_dialog_red.9.png b/java/res/drawable-hdpi/vs_dialog_red.9.png
deleted file mode 100644
index a205560..0000000
--- a/java/res/drawable-hdpi/vs_dialog_red.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/vs_dialog_yellow.9.png b/java/res/drawable-hdpi/vs_dialog_yellow.9.png
deleted file mode 100644
index ce664b6..0000000
--- a/java/res/drawable-hdpi/vs_dialog_yellow.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/vs_popup_mic_edge.png b/java/res/drawable-hdpi/vs_popup_mic_edge.png
deleted file mode 100644
index 4ff6337..0000000
--- a/java/res/drawable-hdpi/vs_popup_mic_edge.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/caution.png b/java/res/drawable-mdpi/caution.png
deleted file mode 100644
index eaef534..0000000
--- a/java/res/drawable-mdpi/caution.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_mic_dialog.png b/java/res/drawable-mdpi/ic_mic_dialog.png
deleted file mode 100644
index 77613ca..0000000
--- a/java/res/drawable-mdpi/ic_mic_dialog.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_suggest_strip_microphone.png b/java/res/drawable-mdpi/ic_suggest_strip_microphone.png
deleted file mode 100644
index 18f314a..0000000
--- a/java/res/drawable-mdpi/ic_suggest_strip_microphone.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_suggest_strip_microphone_swipe.png b/java/res/drawable-mdpi/ic_suggest_strip_microphone_swipe.png
deleted file mode 100644
index ff629b6..0000000
--- a/java/res/drawable-mdpi/ic_suggest_strip_microphone_swipe.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/mic_base.png b/java/res/drawable-mdpi/mic_base.png
deleted file mode 100644
index 53e29ff..0000000
--- a/java/res/drawable-mdpi/mic_base.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/mic_full.png b/java/res/drawable-mdpi/mic_full.png
deleted file mode 100644
index e3e3dfa..0000000
--- a/java/res/drawable-mdpi/mic_full.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/mic_slash.png b/java/res/drawable-mdpi/mic_slash.png
deleted file mode 100644
index d04b563..0000000
--- a/java/res/drawable-mdpi/mic_slash.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/more_keys_divider.png b/java/res/drawable-mdpi/more_keys_divider.png
new file mode 100644
index 0000000..a46284f
--- /dev/null
+++ b/java/res/drawable-mdpi/more_keys_divider.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_language_switch.png b/java/res/drawable-mdpi/sym_keyboard_language_switch.png
new file mode 100644
index 0000000..f30c1b6
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_language_switch.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-mdpi/sym_keyboard_zwj_holo.png
new file mode 100644
index 0000000..70370d8
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_zwj_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-mdpi/sym_keyboard_zwnj_holo.png
new file mode 100644
index 0000000..a69eade
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_zwnj_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/vs_dialog_blue.9.png b/java/res/drawable-mdpi/vs_dialog_blue.9.png
deleted file mode 100644
index cf27e8f..0000000
--- a/java/res/drawable-mdpi/vs_dialog_blue.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/vs_dialog_red.9.png b/java/res/drawable-mdpi/vs_dialog_red.9.png
deleted file mode 100644
index 6c08d5a..0000000
--- a/java/res/drawable-mdpi/vs_dialog_red.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/vs_dialog_yellow.9.png b/java/res/drawable-mdpi/vs_dialog_yellow.9.png
deleted file mode 100644
index 2fb06c2..0000000
--- a/java/res/drawable-mdpi/vs_dialog_yellow.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/vs_popup_mic_edge.png b/java/res/drawable-mdpi/vs_popup_mic_edge.png
deleted file mode 100644
index 4ff6337..0000000
--- a/java/res/drawable-mdpi/vs_popup_mic_edge.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/caution.png b/java/res/drawable-xhdpi/caution.png
deleted file mode 100644
index cfc3f75..0000000
--- a/java/res/drawable-xhdpi/caution.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_mic_dialog.png b/java/res/drawable-xhdpi/ic_mic_dialog.png
deleted file mode 100644
index 5d6399c..0000000
--- a/java/res/drawable-xhdpi/ic_mic_dialog.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_suggest_strip_microphone.png b/java/res/drawable-xhdpi/ic_suggest_strip_microphone.png
deleted file mode 100644
index d65d287..0000000
--- a/java/res/drawable-xhdpi/ic_suggest_strip_microphone.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_suggest_strip_microphone_swipe.png b/java/res/drawable-xhdpi/ic_suggest_strip_microphone_swipe.png
deleted file mode 100644
index 889378a..0000000
--- a/java/res/drawable-xhdpi/ic_suggest_strip_microphone_swipe.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/mic_base.png b/java/res/drawable-xhdpi/mic_base.png
deleted file mode 100644
index 5c060be..0000000
--- a/java/res/drawable-xhdpi/mic_base.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/mic_full.png b/java/res/drawable-xhdpi/mic_full.png
deleted file mode 100644
index 32ffe12..0000000
--- a/java/res/drawable-xhdpi/mic_full.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/mic_slash.png b/java/res/drawable-xhdpi/mic_slash.png
deleted file mode 100644
index 18b2254..0000000
--- a/java/res/drawable-xhdpi/mic_slash.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/more_keys_divider.png b/java/res/drawable-xhdpi/more_keys_divider.png
new file mode 100644
index 0000000..178594b
--- /dev/null
+++ b/java/res/drawable-xhdpi/more_keys_divider.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-xhdpi/sym_keyboard_zwj_holo.png
new file mode 100644
index 0000000..2669427
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_zwj_holo.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo.png
new file mode 100644
index 0000000..75a22b6
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/vs_dialog_blue.9.png b/java/res/drawable-xhdpi/vs_dialog_blue.9.png
deleted file mode 100644
index 3284d78..0000000
--- a/java/res/drawable-xhdpi/vs_dialog_blue.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/vs_dialog_red.9.png b/java/res/drawable-xhdpi/vs_dialog_red.9.png
deleted file mode 100644
index 5af2465..0000000
--- a/java/res/drawable-xhdpi/vs_dialog_red.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/vs_dialog_yellow.9.png b/java/res/drawable-xhdpi/vs_dialog_yellow.9.png
deleted file mode 100644
index 4f50439..0000000
--- a/java/res/drawable-xhdpi/vs_dialog_yellow.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/vs_popup_mic_edge.png b/java/res/drawable-xhdpi/vs_popup_mic_edge.png
deleted file mode 100644
index 1063cb4..0000000
--- a/java/res/drawable-xhdpi/vs_popup_mic_edge.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable/background_voice.xml b/java/res/drawable/background_voice.xml
deleted file mode 100644
index 3b6137d..0000000
--- a/java/res/drawable/background_voice.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <gradient
-        android:startColor="#ff000000"
-        android:endColor="#ff000e29"
-        android:angle="90" />
-</shape>
\ No newline at end of file
diff --git a/java/res/drawable/btn_center.xml b/java/res/drawable/btn_center.xml
index 9998b56..3ac2129 100644
--- a/java/res/drawable/btn_center.xml
+++ b/java/res/drawable/btn_center.xml
@@ -37,4 +37,4 @@
         android:drawable="@drawable/btn_center_default" />
     <item
         android:drawable="@drawable/btn_center_default" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/java/res/layout-sw768dp/recognition_status.xml b/java/res/layout-sw768dp/recognition_status.xml
deleted file mode 100644
index 40bc098..0000000
--- a/java/res/layout-sw768dp/recognition_status.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:background="@drawable/background_voice">
-    <LinearLayout
-            xmlns:android="http://schemas.android.com/apk/res/android"
-            android:id="@+id/popup_layout"
-            android:orientation="vertical"
-            android:layout_height="371dip"
-            android:layout_width="500dip"
-            android:layout_centerInParent="true"
-            android:background="@drawable/vs_dialog_red">
-        <TextView
-                android:id="@+id/text"
-                android:text="@string/voice_error"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:singleLine="true"
-                android:layout_marginTop="10dip"
-                android:textSize="28sp"
-                android:textColor="#ffffff"
-                android:layout_gravity="center"
-                android:visibility="invisible"/>
-        <RelativeLayout
-                android:layout_height="0dip"
-                android:layout_width="match_parent"
-                android:layout_weight="1.0">
-            <com.android.inputmethod.deprecated.voice.SoundIndicator
-                    android:id="@+id/sound_indicator"
-                    android:src="@drawable/mic_full"
-                    android:background="@drawable/mic_base"
-                    android:adjustViewBounds="true"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:layout_centerInParent="true"
-                    android:visibility="gone"/>
-            <ImageView
-                    android:id="@+id/image"
-                    android:src="@drawable/mic_slash"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:layout_centerInParent="true"
-                    android:visibility="visible"/>
-            <ProgressBar
-                    android:id="@+id/progress"
-                    android:indeterminate="true"
-                    android:indeterminateOnly="false"
-                    android:layout_height="60dip"
-                    android:layout_width="60dip"
-                    android:layout_centerInParent="true"
-                    android:visibility="gone"/>
-        </RelativeLayout>
-        <!--
-        The text is set by the code. We specify a random text (voice_error), so the
-        text view does not have a zero height. This is necessary to keep the slash
-        mic and the recording mic is the same position
-        -->
-        <TextView
-                android:id="@+id/language"
-                android:text="@string/voice_error"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:singleLine="true"
-                android:textSize="14sp"
-                android:layout_marginBottom="3dip"
-                android:layout_gravity="center"
-                android:textColor="#ffffff"
-                android:visibility="invisible"/>
-        <Button
-                android:id="@+id/button"
-                android:layout_width="match_parent"
-                android:layout_height="54dip"
-                android:singleLine="true"
-                android:focusable="true"
-                android:text="@string/cancel"
-                android:layout_gravity="center_horizontal"
-                android:background="@drawable/btn_center"
-                android:textColor="#ffffff"
-                android:textSize="19sp" />
-    </LinearLayout>
-</RelativeLayout>
diff --git a/java/res/layout/hint_add_to_dictionary.xml b/java/res/layout/hint_add_to_dictionary.xml
new file mode 100644
index 0000000..73de44f
--- /dev/null
+++ b/java/res/layout/hint_add_to_dictionary.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is derived from suggestion_word.xml without minWidth attribute and padding -->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:textSize="@dimen/suggestion_text_size"
+    android:gravity="center"
+    android:paddingLeft="0dp"
+    android:paddingTop="0dp"
+    android:paddingRight="0dp"
+    android:paddingBottom="0dp"
+    android:focusable="false"
+    android:clickable="false"
+    android:singleLine="true"
+    android:ellipsize="none"
+    style="?attr/suggestionBackgroundStyle" />
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 198e4ca..3863534 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -28,7 +28,7 @@
     <View
         android:id="@+id/key_preview_backing"
         android:layout_width="match_parent"
-        android:layout_height="0dip" />
+        android:layout_height="0dp" />
 
     <!-- On tablets, the suggestions strip is centered with horizontal paddings on both sides
          because width of the landscape mode is too long for the suggestions strip. This
@@ -43,7 +43,7 @@
             android:layout_width="@dimen/suggestions_strip_padding"
             android:layout_height="@dimen/suggestions_strip_height"
             style="?attr/suggestionsStripBackgroundStyle" />
-        <com.android.inputmethod.latin.SuggestionsView
+        <com.android.inputmethod.latin.suggestions.SuggestionsView
             android:id="@+id/suggestions_view"
             android:layout_weight="1.0"
             android:layout_width="0dp"
diff --git a/java/res/layout/key_preview.xml b/java/res/layout/key_preview.xml
index b620d07..6ed892e 100644
--- a/java/res/layout/key_preview.xml
+++ b/java/res/layout/key_preview.xml
@@ -20,8 +20,8 @@
 
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
-    android:layout_height="80sp"
-    android:textSize="40sp"
-    android:minWidth="32dip"
+    android:layout_height="80dp"
+    android:textSize="40dp"
+    android:minWidth="32dp"
     android:gravity="center"
     />
diff --git a/java/res/layout/mini_keyboard.xml b/java/res/layout/more_keys_keyboard.xml
similarity index 84%
rename from java/res/layout/mini_keyboard.xml
rename to java/res/layout/more_keys_keyboard.xml
index 6964ec5..6b2464b 100644
--- a/java/res/layout/mini_keyboard.xml
+++ b/java/res/layout/more_keys_keyboard.xml
@@ -22,12 +22,11 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        style="?attr/miniKeyboardPanelStyle"
+        style="?attr/moreKeysKeyboardPanelStyle"
         >
-    <com.android.inputmethod.keyboard.MiniKeyboardView
+    <com.android.inputmethod.keyboard.MoreKeysKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/mini_keyboard_view"
-            android:layout_alignParentBottom="true"
+            android:id="@+id/more_keys_keyboard_view"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             />
diff --git a/java/res/layout/more_suggestions.xml b/java/res/layout/more_suggestions.xml
index 6aa82e1..49a00c6 100644
--- a/java/res/layout/more_suggestions.xml
+++ b/java/res/layout/more_suggestions.xml
@@ -22,12 +22,11 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        style="?attr/miniKeyboardPanelStyle"
+        style="?attr/moreKeysKeyboardPanelStyle"
         >
-    <com.android.inputmethod.latin.MoreSuggestionsView
+    <com.android.inputmethod.latin.suggestions.MoreSuggestionsView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
             android:id="@+id/more_suggestions_view"
-            android:layout_alignParentBottom="true"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             latin:keyLabelSize="@dimen/suggestion_text_size"
diff --git a/java/res/layout/recognition_status.xml b/java/res/layout/recognition_status.xml
deleted file mode 100644
index a2ddb7c..0000000
--- a/java/res/layout/recognition_status.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:background="@drawable/background_voice">
-    <LinearLayout
-            xmlns:android="http://schemas.android.com/apk/res/android"
-            android:id="@+id/popup_layout"
-            android:orientation="vertical"
-            android:layout_height="371dip"
-            android:layout_width="500dip"
-            android:layout_centerInParent="true"
-            android:background="@drawable/vs_dialog_red">
-        <TextView
-                android:id="@+id/text"
-                android:text="@string/voice_error"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:singleLine="true"
-                android:layout_marginTop="10dip"
-                android:textSize="20sp"
-                android:textColor="#ffffff"
-                android:layout_gravity="center"
-                android:visibility="invisible"/>
-        <RelativeLayout
-                android:layout_height="0dip"
-                android:layout_width="match_parent"
-                android:layout_weight="1.0">
-            <com.android.inputmethod.deprecated.voice.SoundIndicator
-                    android:id="@+id/sound_indicator"
-                    android:src="@drawable/mic_full"
-                    android:background="@drawable/mic_base"
-                    android:adjustViewBounds="true"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:layout_centerInParent="true"
-                    android:visibility="gone"/>
-            <ImageView
-                    android:id="@+id/image"
-                    android:src="@drawable/mic_slash"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:layout_centerInParent="true"
-                    android:visibility="visible"/>
-            <ProgressBar
-                    android:id="@+id/progress"
-                    android:indeterminate="true"
-                    android:indeterminateOnly="false"
-                    android:layout_height="60dip"
-                    android:layout_width="60dip"
-                    android:layout_centerInParent="true"
-                    android:visibility="gone"/>
-        </RelativeLayout>
-        <!--
-        The text is set by the code. We specify a random text (voice_error), so the
-        text view does not have a zero height. This is necessary to keep the slash
-        mic and the recording mic is the same position
-        -->
-        <TextView
-                android:id="@+id/language"
-                android:text="@string/voice_error"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:singleLine="true"
-                android:textSize="15sp"
-                android:layout_marginTop="3dip"
-                android:layout_marginBottom="3dip"
-                android:layout_gravity="center"
-                android:textColor="#ffffff"
-                android:visibility="invisible"/>
-        <Button
-                android:id="@+id/button"
-                android:layout_width="match_parent"
-                android:layout_height="30dip"
-                android:singleLine="true"
-                android:focusable="true"
-                android:text="@string/cancel"
-                android:layout_gravity="center_horizontal"
-                android:background="@drawable/btn_center"
-                android:textColor="#ffffff"
-                android:textSize="15sp" />
-    </LinearLayout>
-</RelativeLayout>
diff --git a/java/res/layout/sound_effect_volume_dialog.xml b/java/res/layout/sound_effect_volume_dialog.xml
index c5b2f10..2946630 100644
--- a/java/res/layout/sound_effect_volume_dialog.xml
+++ b/java/res/layout/sound_effect_volume_dialog.xml
@@ -23,22 +23,22 @@
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_margin="10dip">
+        android:layout_margin="10dp">
     <LinearLayout
         android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:layout_margin="10dip">
+        android:layout_margin="10dp">
         <TextView android:id="@+id/sound_effect_volume_value"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textSize="20dip"/>
+            android:textSize="20dp"/>
     </LinearLayout>
     <SeekBar
         android:id="@+id/sound_effect_volume_bar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:max="100"
-        android:layout_margin="10dip"/>
+        android:layout_margin="10dp"/>
 </LinearLayout>
diff --git a/java/res/layout/suggestion_info.xml b/java/res/layout/suggestion_info.xml
index a364d46..a4ad6df 100644
--- a/java/res/layout/suggestion_info.xml
+++ b/java/res/layout/suggestion_info.xml
@@ -22,6 +22,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:textSize="6sp"
+    android:textSize="6dp"
     android:textColor="@android:color/white"
     style="?attr/suggestionBackgroundStyle" />
diff --git a/java/res/layout/suggestion_preview.xml b/java/res/layout/suggestion_preview.xml
index 3c026ae..856447b 100644
--- a/java/res/layout/suggestion_preview.xml
+++ b/java/res/layout/suggestion_preview.xml
@@ -21,8 +21,8 @@
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:textSize="18sp"
+    android:textSize="18dp"
     android:textColor="?android:attr/textColorPrimaryInverse"
-    android:minWidth="32dip"
+    android:minWidth="32dp"
     android:gravity="center"
     style="?attr/suggestionPreviewBackgroundStyle" />
diff --git a/java/res/layout/vibration_settings_dialog.xml b/java/res/layout/vibration_settings_dialog.xml
index 981ba9b..c9fb6ec 100644
--- a/java/res/layout/vibration_settings_dialog.xml
+++ b/java/res/layout/vibration_settings_dialog.xml
@@ -23,27 +23,27 @@
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_margin="10dip">
+        android:layout_margin="10dp">
     <LinearLayout
         android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:layout_margin="10dip">
+        android:layout_margin="10dp">
         <TextView android:id="@+id/vibration_value"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textSize="20dip"/>
+            android:textSize="20dp"/>
         <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/settings_ms"
-            android:textSize="20dip"/>
+            android:textSize="20dp"/>
     </LinearLayout>
     <SeekBar
         android:id="@+id/vibration_settings"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:max="250"
-        android:layout_margin="10dip"/>
+        android:layout_margin="10dp"/>
 </LinearLayout>
diff --git a/java/res/layout/voice_punctuation_hint.xml b/java/res/layout/voice_punctuation_hint.xml
deleted file mode 100644
index 629a7f2..0000000
--- a/java/res/layout/voice_punctuation_hint.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* 
-**
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     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:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@drawable/keyboard_suggest_strip">
-
-    <!-- TODO: Use dark mic icon. -->
-    <ImageView android:id="@+id/image"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:paddingLeft="8dip"
-            android:paddingRight="8dip"
-            android:layout_gravity="center_horizontal"
-            android:src="@drawable/ic_suggest_strip_microphone"
-    />
-
-    <TextView android:id="@+id/text"
-            android:text="@string/voice_punctuation_hint"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:paddingTop="2dip"
-            android:paddingRight="3dip"
-            android:textSize="13sp"
-            android:textColor="#888888"
-            android:layout_gravity="center_horizontal"
-    />
-
-</LinearLayout>
diff --git a/java/res/layout/voice_swipe_hint.xml b/java/res/layout/voice_swipe_hint.xml
deleted file mode 100644
index 4e8859a..0000000
--- a/java/res/layout/voice_swipe_hint.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* 
-**
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     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:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@drawable/keyboard_suggest_strip"
-        android:gravity="center_horizontal"
-        android:paddingTop="2dip">
-
-    <TextView android:id="@+id/text"
-            android:text="@string/voice_swipe_hint"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:paddingTop="10dip"
-            android:paddingRight="6dip"
-            android:textSize="13sp"
-            android:textColor="#888888"
-            android:layout_gravity="center_horizontal"
-    />
-
-    <ImageView
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_horizontal"
-            android:src="@drawable/ic_suggest_strip_microphone"
-    />
-    
-    <ImageView
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_horizontal"
-            android:src="@drawable/ic_suggest_strip_microphone_swipe"
-    />
-    
-
-</LinearLayout>
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 4c45efe..b2525ad 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android-sleutelbord"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-sleutelbord (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android-sleutelbordinstellings"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropsies"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-korrigering"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-speltoetser"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-speltoetser (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Speltoetser se instellings"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Gebruik nabyheidsdata"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Gebruik \'n sleutelbordagtige nabyheidsalgoritme vir die speltoetser"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Soek kontakname op"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Speltoetser gebruik inskrywings uit jou kontaklys"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreer met sleuteldruk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Klank met sleuteldruk"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Opspring met sleuteldruk"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Tekskorrigering"</string>
     <string name="misc_category" msgid="6894192814868233453">"Ander opsies"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Gevorderde instellings"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opsies vir gevorderde gebruikers"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opsies vir kundiges"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Skakel oor na die ander invoermetodes"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Taal-skakelsleutel dek ook ander invoermetodes"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Onderdruk taalwissel-sleutel"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Sleutelopspringer-wagperiode"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Geen wagperiode nie"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Verstek"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Wys altyd"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Wys in portretmodus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Versteek altyd"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Wys instellingsleutel"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Outokorrigering"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spasiebalk en leestekens korrigeer outomaties woorde wat verkeerd gespel is"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Af"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Gestoor"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Gaan"</string>
     <string name="label_next_key" msgid="362972844525672568">"Volgende"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Vorige"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Klaar"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Stuur"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Geen teks ingevoer nie"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Sleutelkode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift geaktiveer"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Bokas-slot geaktiveer"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aan (tik om te deaktiveer)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Kasslot aan (tik om te deaktiveer)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Vee uit"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simbole"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letters"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Steminvoering"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Glimlag-gesiggie"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Tydperk"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Links-hakie"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Regs-hakie"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dubbelpunt"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Kommapunt"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Uitroepteken"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Vraagteken"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dubbel-aanhalingsteken"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Enkel-aanhalingsteken"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punt"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Vierkantswortel"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Handelsmerk"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Per adres"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Ster"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Pond"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellips"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Onderste dubbel-aanhalingsteken"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Steminvoering"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Steminvoering vir jou taal word nie tans ondersteun nie, maar werk wel in Engels."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Steminvoer gebruik Google se spraakherkenning. "<a href="http://m.google.com/privacy">"Die Mobiel-privaatheidsbeleid"</a>" is van toepassing."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Om steminvoer af te skakel, gaan na invoermetode-instellings."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Om steminvoer te gebruik, druk die mikrofoonknoppie."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Praat nou"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Werkend"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Fout. Probeer asseblief weer."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Kon nie koppel nie"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Fout, te veel spraak."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Oudioprobleem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Bedienerfout"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Geen spraak gehoor nie"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Geen passings gevind nie"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Stemsoek nie geïnstalleer nie"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Wenk:"</b>" Sleep oor die sleutelbord om te praat"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Wenk:"</b>" Probeer volgende keer om leestekens soos \"punt\", \"komma\" of \"vraagteken\" hardop te sê."</string>
-    <string name="cancel" msgid="6830980399865683324">"Kanselleer"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift geaktiveer"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Kasslot geaktiveer"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift gedeaktiveer"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Simboolmodus"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Lettermodus"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Foonmodus"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Foonsimbool-modus"</string>
     <string name="voice_input" msgid="3583258583521397548">"Steminvoerinstellings"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Op hoofsleutelbord"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Op simbolesleutelbord"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofoon op hoofsleutelbord"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrofoon op simbolesleutelbord"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Steminvoer is gedeaktiveer"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Kies invoermetode"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Stel invoermetodes op"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Invoertale"</string>
     <string name="select_language" msgid="3693815588777926848">"Invoertale"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Raak weer om te stoor"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Raak weer om te stoor"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordeboek beskikbaar"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktiveer gebruikerterugvoer"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Help hierdie invoermetode-redigeerder te verbeter deur gebruikstatistiek en omvalverslae outomaties na Google te stuur."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Sleutelbordtema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Duitse QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engels (VK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Bruikbaarheidstudie-modus"</string>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index d5280bc..9279568 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -21,12 +21,14 @@
 <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">"የAndroid ቁልፍሰሌዳ"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"የAndroid ቁልፍ ሰሌዳ (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"የAndroid ቁልፍሰሌዳ ቅንብሮች"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ግቤት አማራጮች"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"የAndroid ማስተካከያ"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android የፊደል ማረሚያ"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android የፊደል ማረሚያ (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"የፊደል አራሚ ቅንብሮች"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"የቀረቤታ ውሂብ ተጠቀም"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"ለፊደል አራሚ የሰሌዳ ቁልፍ አይነት የቀረበ ስልተ ቀመር ተጠቀም"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"የእውቅያ ስሞችን ተመልከት"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"ፊደል አራሚ ከእውቅያ ዝርዝርህ የገቡትን ይጠቀማል"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"በቁልፍመጫንጊዜ አንዝር"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"በቁልፍ መጫን ላይ የሚወጣ ድምፅ"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"ቁልፍ ጫን ላይ ብቅ ባይ"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"ፅሁፍ አስተካክል"</string>
     <string name="misc_category" msgid="6894192814868233453">"ሌሎች አማራጮች"</string>
     <string name="advanced_settings" msgid="362895144495591463">"የላቁ ቅንብሮች"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"ለብቁ ተጠቃሚዎች አማራጮች"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"ለብቁ ተጠቃሚዎች አማራጮች"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"ወደ ሌሎች የግቤት ስልቶች ቀይር"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"የቋንቋ መቀየሪያ ቁልፍ ሌሎች የግቤት ስልቶችንም ይሸፍናል"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"የቋንቋ መቀየሪያ ቁልፍ አፍን"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"የቁልፍ ብቅ ባይ መዘግየትን ያስወገዳል"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"የዘገየ የለም"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ነባሪ"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"ሁልጊዜ አሳይ"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"በቁመት ሁነታ አሳይ"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ሁልጊዜ ደብቅ"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"የቅንብሮች ቁልፍ አሳይ"</string>
     <string name="auto_correction" msgid="4979925752001319458">"በራስ ማስተካከል"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"የቦታ ቁልፍ እና ሥርዓተ ነጥብ በስህተት የተተየቡ ቃላትን  በራስሰር ያስተካክላሉ ።"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ውጪ"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : ተቀምጧል"</string>
     <string name="label_go_key" msgid="1635148082137219148">"ሂድ"</string>
     <string name="label_next_key" msgid="362972844525672568">"በመቀጠል"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"ቀዳሚ"</string>
     <string name="label_done_key" msgid="2441578748772529288">"ተከናውኗል"</string>
     <string name="label_send_key" msgid="2815056534433717444">" ይላኩ"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"ምንም ፅሁፍ አልገባም"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"የቁልፍ ኮድ%d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"ቀይር"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"መቀያየሪያ ቁልፍ ነቅቷል"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"አቢያት ማርጊያነቅቷል"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"ቅያር በርቷል (ለማሰናክል ንካ)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"አቢያት ማድረጊያ ቁልጥ በርቷል (ለማሰናክል ንካ)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"ሰርዝ"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"ምልክቶች"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"ደብዳቤዎች"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"የድምፅ ግቤ ት"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"የፈገግታ ፊት"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"ተመለስ"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"ነጠላ ሰረዝ"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"ክፍለ ጊዜ"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"የግራ ቅንፍ"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"የቀኝ ቅንፍ"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"ሁለት ነጥብ"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"ድርብ ሰረዝ"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"ቃል አጋኖ"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"ጥያቄ ምልክት"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"ድርብ ጥቅስ"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"ነጠላ ትምህርተ ጥቅስ"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"ነጥብ"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"ስክዌር ሩት"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"ዴልታ"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"የንግድምልክት"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"መጠንቀቅ"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"ኮከብ"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"ፓውንድ"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"ዝቅ ያለ ድርብ ትምህርተ ጥቅስ"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"የድምፅ ግቤ ት"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"የድምፅ ግቤት በአሁኑ ጊዜ ለእርስዎን ቋንቋ አይደግፍም፣ ግን በእንግሊዘኛ ይሰራል።"</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"የድምፅ ግቤት የGoogleን ንግግር ለይቶ ማወቂያ ይጠቀማል።"<a href="http://m.google.com/privacy">"የተንቀሳቃሽ ስልክ ግላዊ ፖሊሲ"</a>" ይተገበራል።"</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"የድምፅ ግቤት ለማጥፋት፣ወደ ግቤት ሜተድ ቅንብሮች ሂድ።"</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"የድምፅግቤት ለመጠቀም፣ የማይክራፎንየድምፅ ማጉያ አዝራር ተጫን።"</string>
-    <string name="voice_listening" msgid="467518160751321844">"አሁን ተናገር"</string>
-    <string name="voice_working" msgid="6666937792815731889">"ሥራ ላይ"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"ስህተት፣ እባክዎ እንደገና ይሞክሩ።"</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"ማያያዝ አልተቻለም"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"ስህተት፣ በጣም ብዙ ንግግር።"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"የድምፅ ችግር"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"የአገልጋይ ስህተት"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"ምንም ንግግር አልተሰማም"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"ምንም ተመሳሳይ አልተገኘም።"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"የድምፅ ፍለጋአልተጫነም"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"ምክር፡"</b>" ለመናገር በቁልፍሰሌዳ ላይ አንሸራት"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"ምክር፡"</b>" ሌላ ጊዜ፣  እንደ \"period\", \"comma\", ወይም \"question mark\" ያሉ ስርዓተ ነጥቦችን ለመናገር ሞክር።"</string>
-    <string name="cancel" msgid="6830980399865683324">"ይቅር"</string>
-    <string name="ok" msgid="7898366843681727667">"እሺ"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"ቅያር ቁልፍ ነቅቷል"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"አቢያት ማድረጊያ ነቅቷል"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"ቅያር ተሰናክሏል"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"የምልክቶች ሁኔታ ላይ"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"የደብዳቤዎች ሁኔታ ላይ"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"የስልክ ሁኔታ ላይ"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"የስልክ ምልክቶች ሁኔታ ላይ"</string>
     <string name="voice_input" msgid="3583258583521397548">"የድምፅ ግቤት ቁልፍ"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"በዋናቁልፍ ሰሌዳ ላይ"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"በምልክቶች ቁልፍ ሰሌዳ ላይ"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"ድምፅ ማጉያ በዋናው ቁልፍሰሌዳው ላይ"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"የድምፅ ማጉያ ምልክትበቁልፍ ሰሌዳላይ"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"የድምፅ ግቤት ቦዝኗል"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"የግቤት ሜተድ ምረጥ"</string>
     <string name="configure_input_method" msgid="373356270290742459">"ግቤት ሜተዶችን አዋቀር"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"ቋንቋዎች አግቤት"</string>
     <string name="select_language" msgid="3693815588777926848">"ቋንቋዎች አግቤት"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← ለማስቀመጥ ድጋሚ ንካ"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"ለማስቀመጥ እንደገና ንካ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"መዝገበ ቃላት አለ"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"የተጠቃሚ ግብረ ምላሽ አንቃ"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"ወደ Google የተሰናከለ ሪፖርቶች እና አጠቃቀም ስታስቲክስ በራስ ሰር በመላክ ይህን ግቤት ሜተድ አርትኢ እገዛ ያሻሽላል።"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"የቁልፍ ሰሌዳ ገጽታ"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"የጀመርን QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"እንግሊዘኛ (የታላቋ ብሪታንያ)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"እንግሊዘኛ (ዩ.ኤስ)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"የተገልጋይነት ጥናት ሁነታ"</string>
diff --git a/java/res/values-ar/donottranslate-more-keys.xml b/java/res/values-ar/donottranslate-more-keys.xml
index cde6860..ecad0bb 100644
--- a/java/res/values-ar/donottranslate-more-keys.xml
+++ b/java/res/values-ar/donottranslate-more-keys.xml
@@ -18,94 +18,143 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- \u060c: ARABIC COMMA
-         \u061b: ARABIC SEMICOLON
-         \u061f: ARABIC QUESTION MARK -->
-    <!-- \u0650: ARABIC KASRA
-         \u064e: ARABIC FATHA
-         \u064b: ARABIC FATHATAN
-         \u0640: ARABIC TATWEEL
-         \u064d: ARABIC KASRATAN
-         \u0670: ARABIC LETTER SUPERSCRIPT ALEF
-         \u0656: ARABIC SUBSCRIPT ALEF
-         \u0654: ARABIC HAMZA ABOVE
-         \u0655: ARABIC HAMZA BELOW -->
-    <!-- \u0651: ARABIC SHADDA
-         \u0652: ARABIC SUKUN
-         \u064c: ARABIC DAMMATAN
-         \u0653: ARABIC MADDAH ABOVE
-         \u064f: ARABIC DAMMA -->
+    <!-- U+061F: "؟" ARABIC QUESTION MARK
+         U+060C: "،" ARABIC COMMA
+         U+061B: "؛" ARABIC SEMICOLON -->
+    <!-- U+0650: "ِ" ARABIC KASRA
+         U+064E: "َ" ARABIC FATHA
+         U+064D: "ٍ" ARABIC KASRATAN
+         U+064B: "ً" ARABIC FATHATAN
+         U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+         U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+         U+0655: "ٕ" ARABIC HAMZA BELOW
+         U+0654: "ٔ" ARABIC HAMZA ABOVE -->
+    <!-- U+064F: "ُ" ARABIC DAMMA
+         U+064C: "ٌ" ARABIC DAMMATAN
+         U+0651: "ّ" ARABIC SHADDA
+         U+0652: "ْ" ARABIC SUKUN
+         U+0653: "ٓ" ARABIC MADDAH ABOVE
+         U+0640: "ـ" ARABIC TATWEEL -->
     <!-- In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. -->
-    <!-- TODO: Will introduce "grouping marks" to the more characters specification. -->
-    <string name="more_keys_for_punctuation">"\u060c,\u061b,\u061f,!,:,-,/,\',\",\u0640\u0640\u0640|\u0640,\u064e,\u0650,\u064b,\u064d,\u0670,\u0656,\u0655,\u0654,\u0653,\u0652,\u0651,\u064c,\u064f"</string>
-    <integer name="mini_keyboard_column_for_punctuation">9</integer>
-    <string name="keyhintlabel_for_punctuation">\u064b</string>
-    <string name="keylabel_for_symbols_1">"١"</string>
-    <string name="keylabel_for_symbols_2">"٢"</string>
-    <string name="keylabel_for_symbols_3">"٣"</string>
-    <string name="keylabel_for_symbols_4">"٤"</string>
-    <string name="keylabel_for_symbols_5">"٥"</string>
-    <string name="keylabel_for_symbols_6">"٦"</string>
-    <string name="keylabel_for_symbols_7">"٧"</string>
-    <string name="keylabel_for_symbols_8">"٨"</string>
-    <string name="keylabel_for_symbols_9">"٩"</string>
-    <string name="keylabel_for_symbols_0">"٠"</string>
-    <string name="more_keys_for_symbols_1">1</string>
-    <string name="more_keys_for_symbols_2">2</string>
-    <string name="more_keys_for_symbols_3">3</string>
-    <string name="more_keys_for_symbols_4">4</string>
-    <string name="more_keys_for_symbols_5">5</string>
-    <string name="more_keys_for_symbols_6">6</string>
-    <string name="more_keys_for_symbols_7">7</string>
-    <string name="more_keys_for_symbols_8">8</string>
-    <string name="more_keys_for_symbols_9">9</string>
-    <!-- \u066b: ARABIC DECIMAL SEPARATOR
-         \u066c: ARABIC THOUSANDS SEPARATOR -->
-    <string name="more_keys_for_symbols_0">0,\u066b,\u066c</string>
-    <string name="keylabel_for_comma">\u060c</string>
-    <string name="keylabel_for_f1">\u060c</string>
-    <string name="keylabel_for_symbols_question">\u061f</string>
-    <string name="keylabel_for_symbols_semicolon">\u061b</string>
-    <!-- \u066a: ARABIC PERCENT SIGN -->
-    <string name="keylabel_for_symbols_percent">\u066a</string>
-    <string name="more_keys_for_comma">,</string>
-    <string name="more_keys_for_f1">,</string>
-    <!-- @icon/3 is iconSettingsKey -->
-    <string name="more_keys_for_f1_settings">\\,,\@icon/3|\@integer/key_settings</string>
-    <!-- @icon/7 is iconTabKey -->
-    <string name="more_keys_for_f1_navigate">\\,,\@icon/7|\@integer/key_tab</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',-,:,!,&#x061F;,&#x060C;,&#x061B;,&#x0650;,&#x064E;,&#x064D;,&#x064B;,&#x0656;,&#x0670;,&#x0655;,&#x0654;,&#x064F;,&#x064C;,&#x0651;,&#x0652;,&#x0653;,&#x0640;&#x0640;&#x0640;|&#x0640;,/"</string>
+    <string name="keyhintlabel_for_punctuation">&#x064B;</string>
+    <!-- U+0661: "١" ARABIC-INDIC DIGIT ONE -->
+    <string name="keylabel_for_symbols_1">&#x0661;</string>
+    <!-- U+0662: "٢" ARABIC-INDIC DIGIT TWO -->
+    <string name="keylabel_for_symbols_2">&#x0662;</string>
+    <!-- U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+    <string name="keylabel_for_symbols_3">&#x0663;</string>
+    <!-- U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
+    <string name="keylabel_for_symbols_4">&#x0664;</string>
+    <!-- U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
+    <string name="keylabel_for_symbols_5">&#x0665;</string>
+    <!-- U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
+    <string name="keylabel_for_symbols_6">&#x0666;</string>
+    <!-- U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+    <string name="keylabel_for_symbols_7">&#x0667;</string>
+    <!-- U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
+    <string name="keylabel_for_symbols_8">&#x0668;</string>
+    <!-- U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
+    <string name="keylabel_for_symbols_9">&#x0669;</string>
+    <!-- U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
+    <string name="keylabel_for_symbols_0">&#x0660;</string>
+    <string name="additional_more_keys_for_symbols_1">1</string>
+    <string name="additional_more_keys_for_symbols_2">2</string>
+    <string name="additional_more_keys_for_symbols_3">3</string>
+    <string name="additional_more_keys_for_symbols_4">4</string>
+    <string name="additional_more_keys_for_symbols_5">5</string>
+    <string name="additional_more_keys_for_symbols_6">6</string>
+    <string name="additional_more_keys_for_symbols_7">7</string>
+    <string name="additional_more_keys_for_symbols_8">8</string>
+    <string name="additional_more_keys_for_symbols_9">9</string>
+    <!-- U+066B: "٫" ARABIC DECIMAL SEPARATOR
+         U+066C: "٬" ARABIC THOUSANDS SEPARATOR -->
+    <string name="additional_more_keys_for_symbols_0">0,&#x066B;,&#x066C;</string>
+    <!-- U+060C: "،" ARABIC COMMA -->
+    <string name="keylabel_for_comma">&#x060C;</string>
+    <string name="more_keys_for_comma">"\\,"</string>
+    <string name="keylabel_for_symbols_question">&#x061F;</string>
+    <string name="keylabel_for_symbols_semicolon">&#x061B;</string>
+    <!-- U+066A: "٪" ARABIC PERCENT SIGN -->
+    <string name="keylabel_for_symbols_percent">&#x066A;</string>
     <string name="more_keys_for_symbols_question">\?</string>
     <string name="more_keys_for_symbols_semicolon">;</string>
-    <string name="more_keys_for_symbols_percent">%,‰</string>
-    <!-- \u060c: ARABIC COMMA
-         \u061b: ARABIC SEMICOLON
-         \u061f: ARABIC QUESTION MARK -->
-    <string name="keylabel_for_apostrophe">"\u060c"</string>
+    <!-- U+2030: "‰" PER MILLE SIGN -->
+    <string name="more_keys_for_symbols_percent">%,&#x2030;</string>
+    <!-- U+060C: "،" ARABIC COMMA
+         U+061B: "؛" ARABIC SEMICOLON
+         U+061F: "؟" ARABIC QUESTION MARK -->
+    <string name="keylabel_for_apostrophe">&#x060C;</string>
     <string name="keylabel_for_dash">"."</string>
-    <string name="keyhintlabel_for_apostrophe">"\u061f"</string>
-    <string name="keyhintlabel_for_dash">"\u064b"</string>
-    <string name="more_keys_for_apostrophe">"\u061f,\u061b,!,:,-,/,\',\""</string>
-    <!-- \u0651: ARABIC SHADDA
-         \u0652: ARABIC SUKUN
-         \u064c: ARABIC DAMMATAN
-         \u0653: ARABIC MADDAH ABOVE
-         \u064f: ARABIC DAMMA -->
-    <!-- \u0650: ARABIC KASRA
-         \u064e: ARABIC FATHA
-         \u064b: ARABIC FATHATAN
-         \u0640: ARABIC TATWEEL
-         \u064d: ARABIC KASRATAN -->
-    <!-- \u0670: ARABIC LETTER SUPERSCRIPT ALEF
-         \u0656: ARABIC SUBSCRIPT ALEF
-         \u0654: ARABIC HAMZA ABOVE
-         \u0655: ARABIC HAMZA BELOW -->
+    <string name="keyhintlabel_for_apostrophe">&#x061F;</string>
+    <string name="keyhintlabel_for_dash">&#x064B;</string>
+    <string name="more_keys_for_apostrophe">"&#x061F;,&#x061B;,!,:,-,/,\',\""</string>
+    <!-- U+0651: "ّ" ARABIC SHADDA
+         U+0652: "ْ" ARABIC SUKUN
+         U+064C: "ٌ" ARABIC DAMMATAN
+         U+0653: "ٓ" ARABIC MADDAH ABOVE
+         U+064F: "ُ" ARABIC DAMMA -->
+    <!-- U+0650: "ِ" ARABIC KASRA
+         U+064E: "َ" ARABIC FATHA
+         U+064B: "ً" ARABIC FATHATAN
+         U+0640: "ـ" ARABIC TATWEEL
+         U+064D: "ٍ" ARABIC KASRATAN -->
+    <!-- U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+         U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+         U+0654: "ٔ" ARABIC HAMZA ABOVE
+         U+0655: "ٕ" ARABIC HAMZA BELOW -->
     <!-- In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. -->
-    <string name="more_keys_for_dash">"\u0651,\u0652,\u064c,\u0653,\u064f,\u0650,\u064e,\u064b,\u0640\u0640\u0640|\u0640,\u064d,\u0654,\u0656,\u0655,\u0670"</string>
-    <string name="more_keys_for_bullet">♪</string>
-    <!-- \u066d: ARABIC FIVE POINTED STAR -->
-    <string name="more_keys_for_star">★,\u066d</string>
-    <!-- \ufd3e: ORNATE LEFT PARENTHESIS -->
-    <string name="more_keys_for_left_parenthesis">[,{,&lt;,\ufd3e</string>
-    <!-- \ufd3f: ORNATE RIGHT PARENTHESIS -->
-    <string name="more_keys_for_right_parenthesis">],},&gt;,\ufd3f</string>
+    <string name="more_keys_for_dash">"&#x0651;,&#x0652;,&#x064C;,&#x0653;,&#x064F;,&#x0650;,&#x064E;,&#x064B;,&#x0640;&#x0640;&#x0640;|&#x0640;,&#x064D;,&#x0654;,&#x0656;,&#x0655;,&#x0670;"</string>
+    <!-- U+266A: "♪" EIGHTH NOTE -->
+    <string name="more_keys_for_bullet">&#x266A;</string>
+    <!-- U+2605: "★" BLACK STAR
+         U+066D: "٭" ARABIC FIVE POINTED STAR -->
+    <string name="more_keys_for_star">&#x2605;,&#x066D;</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- U+0029: ")" RIGHT PARENTHESIS -->
+    <integer name="keycode_for_left_parenthesis">0x0029</integer>
+    <!-- U+0028: "(" LEFT PARENTHESIS -->
+    <integer name="keycode_for_right_parenthesis">0x0028</integer>
+    <!-- U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
+         U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+FD3E ORNATE LEFT PARENTHESIS -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+FD3F ORNATE RIGHT PARENTHESIS -->
+    <string name="more_keys_for_left_parenthesis">!fixedColumnOrder!4,&#xFD3E;|&#xFD3F;,&lt;|&gt;,{|},[|]</string>
+    <string name="more_keys_for_right_parenthesis">!fixedColumnOrder!4,&#xFD3F;|&#xFD3E;,&gt;|&lt;,}|{,]|[</string>
+    <!-- U+003E: ">" GREATER-THAN SIGN -->
+    <integer name="keycode_for_less_than">0x003E</integer>
+    <!-- U+003C: "<" LESS-THAN SIGN -->
+    <integer name="keycode_for_greater_than">0x003C</integer>
+    <!-- U+2264: "≤" LESS-THAN OR EQUAL TO
+         U+2265: "≥" GREATER-THAN EQUAL TO
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+         The following characters don't need BIDI mirroring.
+         U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
+    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
+    <!-- U+005D: "]" RIGHT SQUARE BRACKET -->
+    <integer name="keycode_for_left_square_bracket">0x005D</integer>
+    <!-- U+005B: "[" LEFT SQUARE BRACKET -->
+    <integer name="keycode_for_right_square_bracket">0x005B</integer>
+    <!-- U+007D: "}" RIGHT CURLY BRACKET -->
+    <integer name="keycode_for_left_curly_bracket">0x007D</integer>
+    <!-- U+007B: "{" LEFT CURLY BRACKET -->
+    <integer name="keycode_for_right_curly_bracket">0x007B</integer>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
 </resources>
diff --git a/java/res/values-fr-rCH/donottranslate-more-keys.xml b/java/res/values-ar/donottranslate.xml
similarity index 68%
rename from java/res/values-fr-rCH/donottranslate-more-keys.xml
rename to java/res/values-ar/donottranslate.xml
index 561c5e5..57de253 100644
--- a/java/res/values-fr-rCH/donottranslate-more-keys.xml
+++ b/java/res/values-ar/donottranslate.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,9 +18,8 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_y">ÿ</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z">6</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- Symbols that are suggested between words -->
+    <string name="suggested_punctuations">!,?,\\,,:,;,\",(|),)|(,\',-,/,@,_</string>
 </resources>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index dca7365..9daa012 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -21,12 +21,14 @@
 <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">"لوحة مفاتيح Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"لوحة مفاتيح Android ‏(AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"إعدادات لوحة مفاتيح Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"خيارات الإرسال"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"تصحيح Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"التدقيق الإملائي في Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"التدقيق الإملائي في Android‏ (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"إعدادات التدقيق الإملائي"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"استخدام بيانات التقريب"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"استخدام خوارزمية تقريب شبيهة بلوحة المفاتيح لإجراء التدقيق الإملائي"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"بحث في أسماء جهات الاتصال"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"يستخدم المدقق الإملائي إدخالات من قائمة جهات الاتصال"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"اهتزاز عند ضغط مفتاح"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"صوت عند الضغط على مفتاح"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"انبثاق عند ضغط مفتاح"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"تصحيح النص"</string>
     <string name="misc_category" msgid="6894192814868233453">"خيارات أخرى"</string>
     <string name="advanced_settings" msgid="362895144495591463">"الإعدادات المتقدمة"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"خيارات للمستخدمين الخبراء"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"خيارات للخبراء"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"التبديل إلى أسلوب إدخال آخر"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"يغطي مفتاح تبديل اللغات أساليب الإدخال الأخرى أيضًا"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"إيقاف مفتاح تبديل اللغات"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"تأخير إزالة النافذة المنبثقة الأساسية"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"بلا تأخير"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"افتراضي"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"عرض دومًا"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"عرض في وضع رأسي"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"إخفاء دومًا"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"عرض مفتاح الإعدادات"</string>
     <string name="auto_correction" msgid="4979925752001319458">"التصحيح التلقائي"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"تؤدي المسافة والترقيم إلى تصحيح الكلمات المكتوبة بشكل غير صحيح"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"إيقاف"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : تم الحفظ"</string>
     <string name="label_go_key" msgid="1635148082137219148">"تنفيذ"</string>
     <string name="label_next_key" msgid="362972844525672568">"التالي"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"السابق"</string>
     <string name="label_done_key" msgid="2441578748772529288">"تم"</string>
     <string name="label_send_key" msgid="2815056534433717444">"إرسال"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"أ ب ج"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"لم يتم إدخال نص"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"رمز المفتاح %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"العالي"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"تم تمكين العالي"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"تمكين Caps lock"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift يعمل (انقر للتعطيل)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock يعمل (انقر للتعطيل)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"حذف"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"الرموز"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"أحرف"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"إدخال صوتي"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"وجه مبتسم"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"رجوع"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"فاصلة"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"نقطة"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"قوس أيسر"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"قوس أيمن"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"نقطتان"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"فاصلة منقوطة"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"علامة التعجب"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"علامة استفهام"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"علامة الاقتباس المزدوجة"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"علامة الاقتباس المفردة"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"نقطة"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"جذر تربيعي"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"باي"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"دلتا"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"علامة تجارية"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"رعاية"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"نجمة"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"جنيه"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"علامة حذف"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"علامة الاقتباس المزدوجة السفلية"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"الإدخال الصوتي"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"الإدخال الصوتي غير معتمد حاليًا للغتك، ولكنه يعمل باللغة الإنجليزية."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"يستخدم الإدخال الصوتي خاصية التعرف على الكلام من Google. تنطبق "<a href="http://m.google.com/privacy">"سياسة خصوصية الجوال"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"لإيقاف الإدخال الصوتي، انتقل إلى إعدادات طريقة الإدخال."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"لاستخدام الإدخال الصوتي، اضغط على زر الميكروفون."</string>
-    <string name="voice_listening" msgid="467518160751321844">"تحدث الآن"</string>
-    <string name="voice_working" msgid="6666937792815731889">"العمل"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"خطأ. الرجاء المحاولة مرة أخرى."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"تعذر الاتصال"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"خطأ، كلام أكثر مما ينبغي."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"مشكلة بالإعدادات الصوتية"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"خطأ في الخادم"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"لم يتم سماع كلام"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"لم يتمّ العثور على أية تطابقات"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"لم يتم تثبيت البحث الصوتي"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"تلميح:"</b>" مرر عبر لوحة المفاتيح للتحدث"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"تلميح:"</b>" جرب في المرة التالية نطق الترقيم مثل \"نقطة\" أو \"فاصلة\" أو \"علامة استفهام\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"إلغاء"</string>
-    <string name="ok" msgid="7898366843681727667">"موافق"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"تم تمكين Shift"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"تم تمكين Caps lock"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"تم تعطيل Shift"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"وضع الرموز"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"وضع الأحرف"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"وضع الهاتف"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"وضع رموز الهاتف"</string>
     <string name="voice_input" msgid="3583258583521397548">"مفتاح الإدخال الصوتي"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"لوحة مفاتيح رئيسية"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"لوحة مفاتيح الرموز"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"ميكروفون على لوحة مفاتيح رئيسية"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"ميكروفون على لوحة مفاتيح الرموز"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"الإدخال الصوتي مُعطل"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"تحديد طريقة الإرسال"</string>
     <string name="configure_input_method" msgid="373356270290742459">"تهيئة طرق الإدخال"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"لغات الإدخال"</string>
     <string name="select_language" msgid="3693815588777926848">"لغات الإدخال"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← المس مرة أخرى للحفظ"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"القاموس متاح"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"تمكين ملاحظات المستخدم"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"المساعدة في تحسين محرر طريقة الإرسال هذا من خلال إرسال إحصاءات الاستخدام وتقارير الأعطال تلقائيًا إلى Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"مظهر لوحة المفاتيح"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"الألمانية (QWERTY)"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"الإنجليزية (المملكة المتحدة)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"الإنجليزية (الولايات المتحدة)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"وضع سهولة الاستخدام"</string>
diff --git a/java/res/values-be/donottranslate-more-keys.xml b/java/res/values-be/donottranslate-more-keys.xml
new file mode 100644
index 0000000..835553a
--- /dev/null
+++ b/java/res/values-be/donottranslate-more-keys.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+045E: "ў" CYRILLIC SMALL LETTER SHORT U -->
+    <string name="keylabel_for_east_slavic_row1_9">&#x045E;</string>
+    <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
+    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+    <string name="keylabel_for_east_slavic_row3_5">&#x0456;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_ha">&#x044A;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
+</resources>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 1ed944c..ab00be9 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -21,12 +21,14 @@
 <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">"Клавіятура Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавіятура Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Налады клавіятуры Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Параметры ўводу"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Папраўкі Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Iнструмент праверкi правапiсу для Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Iнструмент праверкi правапiсу для Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Налады праверкі арфаграфіі"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Выкарыстоўвайць дадзеныя аб блізкасці"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Для праверкі арфаграфіі выкарыстоўваць алгарытм блізкасці, падобны на клавіятуру"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукаць імёны кантактаў"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Модуль праверкі правапісу выкарыстоўвае запісы са спісу кантактаў"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вібрацыя пры націску клавіш"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Гук пры націску"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Па націску на клавішы ўсплывае акно"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Выпраўленне тэксту"</string>
     <string name="misc_category" msgid="6894192814868233453">"Іншыя параметры"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Адмысловыя налады"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Парам. для дасведч. карыст."</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Функцыi для спецыялістаў"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Перакл. да інш. спос. ув."</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Кнопка пераключэння мовы звязана i з iншымi спосабамi ўводу"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Забаранiць кнопку пераключэння мовы"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Затрым. скр. падк. клав."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Няма затрымкі"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Па змаўчанні"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Заўсёды паказваць"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Паказаць у партрэтным рэжыме"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Заўседы хаваць"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Паказаць умоўныя азначэнні налад"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Аўтамат. выпраўленне"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Прабелы і пунктуацыйныя знакі дазваляюць аўтаматычна выпраўляць памылкова ўведзеныя словы"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Адключаны"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Захаваныя"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Пачаць"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далей"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Назад"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Гатова"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Адправіць"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Тэкст не ўведзены"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Клавішны код %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Зрух"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Зрух уключаны"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock уключаны"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift уключаны (націснiце, каб адключыць)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock уключаны (націснiце, каб адключыць)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Выдаліць"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Сімвалы"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Літары"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Галасавы ўвод"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Смайлік"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Увод"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Коска"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Кропка"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Адчыняючая дужка"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Дужка, якая зачыняе"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Двукроп\'е"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Кропка з коскай"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Клічнік"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Пытальнік"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Двукоссі"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Паўдвукоссі"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Кропка"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Квадратны корань"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Пі"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Дэльта"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Гандлёвая марка"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Працэнт"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Пазначыць"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Фунт"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Шматкроп\'е"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Нізкія падвойныя двукоссі"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Галасавы ўвод"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Галасавы ўвод пакуль не падтрымліваецца для вашай мовы, але працуе на англійскай мове."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Галасавы набор выкарыстоўвае распазнанне гаворкі Google. Ужываецца "<a href="http://m.google.com/privacy">"палiтыка прыватнасцi для мабiльных прылад"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Каб адключыць галасавы ўвод, перайдзіце ў налады метаду ўводу."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Каб выкарыстоўваць галасавы ўвод, націсніце кнопку мікрафона."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Гаварыце"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Апрацоўка"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Памылка. Паспрабуйце яшчэ."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Не атрымалася ўсталяваць падключэнне"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Памылка, зашмат гаворкі."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Праблема з гукам"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Памылка сервера"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Не чуваць гаворку"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Няма супадзенняў"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Галасавы пошук не ўсталяваны"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Падказка:"</b>" Правядзіце пальцам праз клавіятуру, каб казаць"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Падказка:"</b>" У наступны раз паспрабуйце прагаворваць такiя знакі пунктуацыі, як \"кропка\", \"коска\" або \"пытальнік\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Адмяніць"</string>
-    <string name="ok" msgid="7898366843681727667">"ОК"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift уключаны"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock уключаны"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift адключаны"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Рэжым знакаў"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Рэжым лiтар"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Рэжым тэлефона"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Рэжым тэлефонных знакаў"</string>
     <string name="voice_input" msgid="3583258583521397548">"Ключ галасавога ўводу"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"На асн. клавіятуры"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"На сімв. клавіятуры"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Мік. на асн. клав."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Мік. на сімв. клав."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Галасавы набор адкл."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Выберыце метад уводу"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Налада метадаў уводу"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Мовы ўводу"</string>
     <string name="select_language" msgid="3693815588777926848">"Мовы ўводу"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"... Дакраніцеся яшчэ раз, каб захаваць"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Слоўнік даступны"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Уключыць зваротную сувязь з карыстальнікамі"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Дапамажыце палепшыць гэты рэдактар ​​метаду ўводу, аўтаматычна адпраўляючы статыстыку выкарыстання і справаздачы аб збоях Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Тэма клавіятуры"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Нямецкая QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Англійская (ЗК)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Англійская (ЗША)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Рэжым даследвання выкарыстальнасці"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 30fe132..c7c7a25 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -21,12 +21,14 @@
 <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">"Клавиатура на Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавиатура на Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Настройки на клавиатурата на Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Корекция на Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Програма за правописна проверка за Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Програма за правописна проверка за Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Настройки за проверка на правописа"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Използване на близост"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Проверка на правописа: Използвайте алгоритъм за близост"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Търсене на имена"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"За проверка на правописа се ползват записи от списъка с контакти"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Да вибрира при натискане на клавиш"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук при натискане на клавиш"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Изскачащ прозорец при натискане на клавиш"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Корекция на текста"</string>
     <string name="misc_category" msgid="6894192814868233453">"Други опции"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Разширени настройки"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Опции за потребителите експерти"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Опции за експерти"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Други методи за въвеждане"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Клавишът за превкл. на езика обхваща и други методи за въвеждане"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Потискане"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Отхвърляне на подсказката"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без задържане"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По подразбиране"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Винаги да се показва"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Показване с вертикална ориентация"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Винаги да се скрива"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Показване на клавиша за настройки"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Автоко"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Клавишът за интервал и пунктуация авт. поправя сгрешени думи"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Изкл."</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Запазено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Старт"</string>
     <string name="label_next_key" msgid="362972844525672568">"Напред"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Пред."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Изпращане"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"АБВ"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Няма въведен текст"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Код на клавишa %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"„Shift“ е активиран"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"„Caps Lock“ е активиран"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"„Shift“ е включен (докоснете за деактивиране)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"„Caps lock“ е включен (докоснете за деактивиране)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Символи"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Букви"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Гласово въвеждане"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Усмивка"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Запетая"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Точка"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Лява кръгла скоба"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Дясна кръгла скоба"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Двоеточие"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Точка и запетая"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Удивителен знак"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Въпросителен знак"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Двойни кавички"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Единични кавички"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Точка"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Корен квадратен"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Пи"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Делта"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Запазена марка"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"По адрес"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Звездичка"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Диез"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Многоточие"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Долни двойни кавички"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Гласово въвеждане"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"За вашия език понастоящем не се поддържа гласово въвеждане, но можете да го използвате на английски."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Гласовото въвеждане използва функцията на Google за разпознаване на говор. В сила е "<a href="http://m.google.com/privacy">"Декларацията за поверителност за мобилни устройства"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"За да изключите гласовото въвеждане, отворете настройките за метода за въвеждане."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"За да използвате гласово въвеждане, натиснете бутона на микрофона."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Говорете сега"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Обработва се"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Грешка. Моля, опитайте отново."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Не можа да се свърже"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Грешка, твърде много речева информация."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Аудиопроблем"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Грешка в сървъра"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Не се чува реч"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Нямаше съответствия"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Не е инсталирано гласово търсене"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Съвет:"</b>" Прокарайте палец през клавиатурата, за да говорите"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Съвет:"</b>" Следващия път опитайте да произнесете знаците за пунктуация, напр. „точка“, „запетая“ или „въпросителен знак“."</string>
-    <string name="cancel" msgid="6830980399865683324">"Отказ"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"„Shift“ е активиран"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"„Caps Lock“ е активиран"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"„Shift“ е деактивиран"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Режим  за символи"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Режим за букви"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Режим  за телефон"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Режим за символи на телефона"</string>
     <string name="voice_input" msgid="3583258583521397548">"Клавиш за гласово въвеждане"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"На осн. клавиатура"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"На клав. на симв."</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Микр. на осн. клав."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Микр. на клав. на симв."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Глас. въвежд. е деакт."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Избор на метод на въвеждане"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Конфигуриране на въвеждането"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Входни езици"</string>
     <string name="select_language" msgid="3693815588777926848">"Езици за въвеждане"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Докоснете отново, за да запазите"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Има достъп до речник"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Активиране на отзивите от потребителите"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Помогнете за подобряването на този редактор за въвеждане чрез автоматично изпращане до Google на статистически данни за употребата и сигнали за сривове."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема на клавиатурата"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"немски, „QWERTY“"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"английски (Великобритания)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"английски (САЩ)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим за изучаване на използваемостта"</string>
diff --git a/java/res/values-ca/donottranslate-more-keys.xml b/java/res/values-ca/donottranslate-more-keys.xml
index bd9fb7c..baa23bf 100644
--- a/java/res/values-ca/donottranslate-more-keys.xml
+++ b/java/res/values-ca/donottranslate-more-keys.xml
@@ -18,12 +18,56 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,á,ä,â,ã,å,ą,æ,ā,ª</string>
-    <string name="more_keys_for_e">3,è,é,ë,ê,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,í,ï,ì,î,į,ī</string>
-    <string name="more_keys_for_o">9,ò,ó,ö,ô,õ,ø,œ,ō,º</string>
-    <string name="more_keys_for_u">7,ú,ü,ù,û,ū</string>
-    <string name="more_keys_for_n">ñ,ń</string>
-    <string name="more_keys_for_c">ç,ć,č</string>
-    <string name="more_keys_for_l">ŀ,ł</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E1;,&#x00E4;,&#x00E2;,&#x00E3;,&#x00E5;,&#x0105;,&#x00E6;,&#x0101;,&#x00AA;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E8;,&#x00E9;,&#x00EB;,&#x00EA;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EF;,&#x00EC;,&#x00EE;,&#x012F;,&#x012B;</string>
+    <!-- U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+    <string name="more_keys_for_o">&#x00F2;,&#x00F3;,&#x00F6;,&#x00F4;,&#x00F5;,&#x00F8;,&#x0153;,&#x014D;,&#x00BA;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x00F9;,&#x00FB;,&#x016B;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+    <!-- U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+         U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
+    <string name="more_keys_for_l">&#x0140;,&#x0142;</string>
 </resources>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index a58d8fa..a530ac1 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -21,12 +21,14 @@
 <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">"Teclat Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclat d\'Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Configuració del teclat d\'Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcions d\'entrada"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correcció d\'Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corrector ortogràfic d\'Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corrector ortogràfic d\'Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Configuració de la correcció ortogràfica"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Utilitza les dades de proximitat"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Utilitza un algorisme de proximitat similar al teclat per comprovar l\'ortografia"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca noms de contactes"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortogràfic utilitza entrades de la llista de cont."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibra en prémer tecles"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"So en prémer una tecla"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Finestra emergent en prémer un botó"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Correcció de text"</string>
     <string name="misc_category" msgid="6894192814868233453">"Altres opcions"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Configuració avançada"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opcions per a usuaris experts"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opcions per a experts"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Canvia mètode d\'entrada"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"La tecla de canvi d\'idioma també cobreix altres mètodes d\'entrada"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Suprimeix tecla d\'idioma"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Retard d\'om. em. de tecla"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sense retard"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminat"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostra sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostra en mode vertical"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Amaga sempre"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Mostra la tecla de configuració"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Correcció automàtica"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Barra espaiadora i punt. correg. autom. paraules mal escrites"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactiva"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: desada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Vés"</string>
     <string name="label_next_key" msgid="362972844525672568">"Següent"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Ant."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Fet"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Envia"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No s\'ha introduït cap text"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Clau de codi %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Maj activat"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Bloq Maj activat"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Maj activat (pica per desactivar)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Bloq Maj activat (pica per desactivar)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Supr"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbols"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Lletres"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Entrada de veu"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Cara somrient"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Retorn"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Coma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punt"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Parèntesi esquerre"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Parèntesi dret"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Coma"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Punt i coma"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Signe d\'admiració"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Signe d\'interrogació"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Cometes dobles"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Cometes simples"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punt"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Arrel quadrada"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marca comercial"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Percentatge"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Destaca"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Coixinet"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Punts suspensius"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Cometes angulars"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Entrada de veu"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualment, l\'entrada de veu no és compatible amb el vostre idioma, però funciona en anglès."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"L\'entrada de veu utilitza el reconeixement de veu de Google. S\'hi aplica la "<a href="http://m.google.com/privacy">"Política de privadesa de Google per a mòbils"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Per desactivar l\'entada de veu, vés a la configuració del mètode d\'entrada."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Per utilitzar l\'entrada de veu, prem el botó del micròfon."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Parleu ara"</string>
-    <string name="voice_working" msgid="6666937792815731889">"S\'està treballant"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Error. Torneu-ho a provar."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"No s\'ha pogut connectar"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Error; s\'ha parlat massa."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema d\'àudio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Error del servidor"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"No s\'escolten paraules"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"No hi ha resultats"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Cerca per veu no instal·lada"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Consell:"</b>" Feu lliscar el dit pel teclat per parlar"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Suggeriment:"</b>" La propera vegada, proveu de dir la puntuació, com ara \"punt\", \"coma\" o \"interrogant\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Cancel·la"</string>
-    <string name="ok" msgid="7898366843681727667">"D\'acord"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Maj activat"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Bloq Maj activat"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Maj desactivat"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mode de símbols"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mode de lletres"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mode de telèfon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mode de símbols de telèfon"</string>
     <string name="voice_input" msgid="3583258583521397548">"Clau de l\'entrada de veu"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Al teclat principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Al tecl. de símb."</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micro en tecl. princ."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micro en tecl. símb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entr. veu desactiv."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Selecciona el mètode d\'entrada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configura mètodes d\'entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomes d\'entrada"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomes d\'entrada"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Torna a tocar per desar"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Torna a tocar per desar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionari disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activa els comentaris de l\'usuari"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Ajuda a millorar aquest editor de mètodes d\'entrada enviant automàticament estadístiques d\'ús i informes de bloqueigs a Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema del teclat"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY alemany"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglès (Regne Unit)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglès (EUA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode d\'estudi d\'usabilitat"</string>
diff --git a/java/res/values-cs/donottranslate-more-keys.xml b/java/res/values-cs/donottranslate-more-keys.xml
index 70b3f3e..9af6794 100644
--- a/java/res/values-cs/donottranslate-more-keys.xml
+++ b/java/res/values-cs/donottranslate-more-keys.xml
@@ -18,17 +18,70 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">á,à,â,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,é,ě,è,ê,ë,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,í,î,ï,ì,į,ī</string>
-    <string name="more_keys_for_o">9,ó,ö,ô,ò,õ,œ,ø,ō</string>
-    <string name="more_keys_for_u">7,ú,ů,û,ü,ù,ū</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
-    <string name="more_keys_for_n">ň,ñ,ń</string>
-    <string name="more_keys_for_c">č,ç,ć</string>
-    <string name="more_keys_for_y">ý,ÿ</string>
-    <string name="more_keys_for_d">ď</string>
-    <string name="more_keys_for_r">4,ř</string>
-    <string name="more_keys_for_t">5,ť</string>
-    <string name="more_keys_for_z">6,ž,ź,ż</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E0;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x011B;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EE;,&#x00EF;,&#x00EC;,&#x012F;,&#x012B;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F6;,&#x00F4;,&#x00F2;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x016F;,&#x00FB;,&#x00FC;,&#x00F9;,&#x016B;</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE -->
+    <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;</string>
+    <!-- U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+         U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x0148;,&#x00F1;,&#x0144;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x010D;,&#x00E7;,&#x0107;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+010F: "ď" LATIN SMALL LETTER D WITH CARON -->
+    <string name="more_keys_for_d">&#x010F;</string>
+    <!-- U+0159: "ř" LATIN SMALL LETTER R WITH CARON -->
+    <string name="more_keys_for_r">&#x0159;</string>
+    <!-- U+0165: "ť" LATIN SMALL LETTER T WITH CARON -->
+    <string name="more_keys_for_t">&#x0165;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017A;,&#x017C;</string>
 </resources>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index d57ccaa..0e7ad8b 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -21,12 +21,14 @@
 <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">"Klávesnice Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Klávesnice Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavení klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávání textu a dat"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Opravy Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kontrola pravopisu Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kontrola pravopisu Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavení kontroly pravopisu"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Použít údaje o blízkosti"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Při kontrole pravopisu uvažovat blízkost písmen na klávesnici"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhledat kontakty"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kontrola pravopisu používá záznamy z vašeho seznamu kontaktů."</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Zobrazit znaky při stisku klávesy"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Oprava textu"</string>
     <string name="misc_category" msgid="6894192814868233453">"Další možnosti"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Pokročilá nastavení"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Možnosti pro zkušené uživatele"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Možnosti pro odborníky"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Přep. na jiné metody zad."</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Klávesa pro přepínání jazyka ovládá i další metody zadávání"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Zakázat kl. přep. jazyka"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Prodleva vysk. okna kláv."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez prodlevy"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Výchozí"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vždy zobrazovat"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Zobrazit v režimu na výšku"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vždy skrývat"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Zobrazit klávesu Nastavení"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automatické opravy"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Stisknutím mezerníku a interpunkce se automaticky opravují chybně napsaná slova"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuto"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Uloženo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Přejít"</string>
     <string name="label_next_key" msgid="362972844525672568">"Další"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Před."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Hotovo"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Odeslat"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Není zadán žádný text"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kód klávesy %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Klávesa Shift je aktivní"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Klávesa Caps Lock je aktivní"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Klávesa Shift je zapnutá (vypnete ji klepnutím)."</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Klávesa Caps Lock je zapnutá (vypnete ji klepnutím)."</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboly"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Písmena"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Hlasový vstup"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smajlík"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Čárka"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Tečka"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Levá závorka"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Pravá závorka"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dvojtečka"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Středník"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Vykřičník"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Otazník"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Uvozovky"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Apostrof"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Tečka"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Odmocnina"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pí"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Ochranná známka"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Procento"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Hvězdička"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Libra"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Tři tečky"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Uvozovky dole"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Hlasový vstup"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Pro váš jazyk aktuálně není hlasový vstup podporován, ale funguje v angličtině."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Hlasový vstup používá rozpoznávání hlasu Google a vztahují se na něj "<a href="http://m.google.com/privacy">"Zásady ochrany osobních údajů pro mobilní služby"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Chcete-li vypnout hlasový vstup, přejděte do nastavení metod vstupu."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Chcete-li použít hlasový vstup, stiskněte tlačítko mikrofonu."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Mluvte"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Probíhá zpracování"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Chyba. Zkuste to prosím znovu."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Připojení se nezdařilo."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Chyba, řeč je příliš dlouhá."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problém se zvukem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Chyba serveru"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nebyla detekována žádná řeč."</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nebyly nalezeny žádné shody"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Hlasové vyhledávání není nainstalováno"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Nápověda:"</b>" Chcete-li aktivovat hlasový vstup, přejeďte prstem přes klávesnici."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Nápověda:"</b>" Příště zkuste vyslovit interpunkci, například „tečka“, „čárka“ nebo „otazník“."</string>
-    <string name="cancel" msgid="6830980399865683324">"Zrušit"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Klávesa Shift je aktivní"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Klávesa Caps Lock je aktivní"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Klávesa Shift je neaktivní"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Režim symbolů"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Režim písmen"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Režim telefonu"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Režim telefonních symbolů"</string>
     <string name="voice_input" msgid="3583258583521397548">"Klíč hlasového vstupu"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Na hlavní klávesnici"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Na klávesnici se symb."</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr. na hlav. kláv."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. na kláv. se symb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hlasový vstup vypnut"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Výběr metody zadávání dat"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurace metod vstupu"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Vstupní jazyky"</string>
     <string name="select_language" msgid="3693815588777926848">"Jazyky vstupu"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Dalším dotykem slovo uložíte"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"K dispozici je slovník"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktivovat zasílání statistik užívání a zpráv o selhání"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Automatickým zasíláním statistik o užívání editoru zadávání dat a zpráv o jeho selhání do Googlu můžete přispět k vylepšení tohoto nástroje."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Motiv klávesnice"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"němčina (QWERTY)"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"angličtina (Spojené království)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angličtina (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Režim studie použitelnosti"</string>
diff --git a/java/res/values-da/donottranslate-more-keys.xml b/java/res/values-da/donottranslate-more-keys.xml
index 12c1ebf..acc0c53 100644
--- a/java/res/values-da/donottranslate-more-keys.xml
+++ b/java/res/values-da/donottranslate-more-keys.xml
@@ -18,18 +18,54 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">á,ä,à,â,ã,ā</string>
-    <string name="more_keys_for_e">3,é,ë</string>
-    <string name="more_keys_for_i">8,í,ï</string>
-    <string name="more_keys_for_o">9,ó,ô,ò,õ,œ,ō</string>
-    <string name="more_keys_for_u">7,ú,ü,û,ù,ū</string>
-    <string name="more_keys_for_s">ß,ś,š</string>
-    <string name="more_keys_for_n">ñ,ń</string>
-    <string name="more_keys_for_y">6,ý,ÿ</string>
-    <string name="more_keys_for_d">ð</string>
-    <string name="more_keys_for_l">ł</string>
-    <string name="keylabel_for_scandinavia_row2_10">æ</string>
-    <string name="keylabel_for_scandinavia_row2_11">ø</string>
-    <string name="more_keys_for_scandinavia_row2_10">ä</string>
-    <string name="more_keys_for_scandinavia_row2_11">ö</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E4;,&#x00E0;,&#x00E2;,&#x00E3;,&#x0101;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00EB;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EF;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F4;,&#x00F2;,&#x00F5;,&#x0153;,&#x014D;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x00FB;,&#x00F9;,&#x016B;</string>
+    <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+00F0: "ð" LATIN SMALL LETTER ETH -->
+    <string name="more_keys_for_d">&#x00F0;</string>
+    <!-- U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
+    <string name="more_keys_for_l">&#x0142;</string>
+    <!-- U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE -->
+    <string name="keylabel_for_nordic_row1_11">&#x00E5;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="keylabel_for_nordic_row2_10">&#x00E6;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="keylabel_for_nordic_row2_11">&#x00F8;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="more_keys_for_nordic_row2_10">&#x00E4;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="more_keys_for_nordic_row2_11">&#x00F6;</string>
 </resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index b91acaa..cccd088 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android-tastatur"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-tastatur (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android-tastatur-indstillinger"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Indstillinger for input"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-rettelse"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-stavekontrol"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-stavekontrol (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Indstillinger for stavekontrol"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Brug nærhedsdata"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Brug en tastaturlignende nærhedsalgoritme til stavekontrol"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå kontaktnavne op"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Stavekontrollen bruger poster fra listen over kontaktpersoner"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibration ved tastetryk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetryk"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop op ved tastetryk"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Tekstkorrigering"</string>
     <string name="misc_category" msgid="6894192814868233453">"Andre valgmuligheder"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Avancerede indstillinger"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Muligheder for ekspertbrugere"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Muligheder for eksperter"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Skift inputmetode"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Tasten til sprogskift gælder også for andre inputmetoder"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Ignorer sprogskifttast"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Forsink. afvis. af taste-pop op"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Ingen forsink."</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vis altid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Vis i portrættilstand"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Skjul altid"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Vis indstillingsnøgle"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automatisk retning"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Mellemrumstast og tegnsætning retter automatisk forkerte ord"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Fra"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Gemt"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Gå"</string>
     <string name="label_next_key" msgid="362972844525672568">"Næste"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Forr."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Udfør"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Send"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Der er ingen indtastet tekst"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tastekode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift-tast"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift-tasten er aktiveret"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock er aktiveret"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Skift er slået til (tryk for at deaktivere)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock er slået til (tryk for at deaktivere)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Slet"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboler"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Bogstaver"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Stemmeinput"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Tilbage"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punktum"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Venstre parentes"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Højre parentes"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Kolon"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikolon"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Udråbstegn"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Spørgsmålstegn"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dobbelt anførselstegn"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Enkelt anførselstegn"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punktum"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Kvadratrod"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Varemærke"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"De bedste hilsner"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Stjerne"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Pund"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipse"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Lave dobbelte anførelsestegn"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Stemmeinput"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Stemmeinput understøttes i øjeblikket ikke for dit sprog, men fungerer på engelsk."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Stemmeinput anvender Googles stemmegenkendelse. "<a href="http://m.google.com/privacy">"Fortrolighedspolitikken for mobilenheder"</a>" gælder."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Slå stemmeinput fra i indstillingerne for inputmetode."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Brug stemmeinput ved at trykke på mikrofonknappen."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Tal nu"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Arbejder"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Fejl. Prøv igen."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Kunne ikke oprette forbindelse"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Fejl. For meget tale."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Lydproblem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Serverfejl"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Der høres ingen tale"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Der blev ikke fundet nogen matches"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Stemmesøgning er ikke installeret"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Tip:"</b>" Glid hen over tastaturet for at tale"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Tip:"</b>" Næste gang kan du forsøge at sige tegnsætning, f.eks. \"punktum\", \"komma\" eller \"spørgsmålstegn\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Annuller"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Skift er aktiveret"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock er aktiveret"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Skift er deaktiveret"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symboltilstand"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Bogstavtilstand"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefontilstand"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefonsymboltilstand"</string>
     <string name="voice_input" msgid="3583258583521397548">"Nøgle til stemmeinput"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"På hovedtastatur"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"På symboltastatur"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mik. på hovedtastatur"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mik. på symboltastatur"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Stemmeinput deaktiveret"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Vælg inputmetode"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurer inputmetoder"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Inputsprog"</string>
     <string name="select_language" msgid="3693815588777926848">"Inputsprog"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tryk igen for at gemme"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tryk igen for at gemme"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbog er tilgængelig"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktiver brugerfeedback"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Vær med til at forbedre denne inputmetode ved at sende anvendelsesstatistikker og rapporter om nedbrud til Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturtema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Tysk QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engelsk (Storbritannien)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engelsk (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tilstand for brugsstudie"</string>
diff --git a/java/res/values-de/config.xml b/java/res/values-de/donottranslate-config.xml
similarity index 100%
rename from java/res/values-de/config.xml
rename to java/res/values-de/donottranslate-config.xml
diff --git a/java/res/values-de/donottranslate-more-keys.xml b/java/res/values-de/donottranslate-more-keys.xml
index 80aa32a..562e574 100644
--- a/java/res/values-de/donottranslate-more-keys.xml
+++ b/java/res/values-de/donottranslate-more-keys.xml
@@ -18,12 +18,37 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ä,â,à,á,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,ė</string>
-    <string name="more_keys_for_o">9,ö,ô,ò,ó,õ,œ,ø,ō</string>
-    <string name="more_keys_for_u">7,ü,û,ù,ú,ū</string>
-    <string name="more_keys_for_s">ß,ś,š</string>
-    <string name="more_keys_for_n">ñ,ń</string>
-    <string name="more_keys_for_y"></string>
-    <string name="more_keys_for_z">6</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E4;,&#x00E2;,&#x00E0;,&#x00E1;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
+    <!-- U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE -->
+    <string name="more_keys_for_e">&#x0117;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F6;,&#x00F4;,&#x00F2;,&#x00F3;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FC;,&#x00FB;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
 </resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index d329f32..cf06781 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android-Tastatur"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-Tastatur (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android-Tastatureinstellungen"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Eingabeoptionen"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Rechtschreibprüfung für Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-Rechtschreibprüfung"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-Rechtschreibprüfung (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Einstellungen für Rechtschreibprüfung"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Näherungsdaten verwenden"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Tastaturähnl. Abstandsalgorith. für Rechtschreibprüfung verwenden"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktnamen prüfen"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Rechtschreibprüfung verwendet Einträge aus Ihrer Kontaktliste."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Bei Tastendruck vibrieren"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ton bei Tastendruck"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up bei Tastendruck"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Textkorrektur"</string>
     <string name="misc_category" msgid="6894192814868233453">"Sonstige Optionen"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Erweiterte Einstellungen"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Optionen für Experten"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Optionen für Experten"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Eingabemethoden wechseln"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Sprachwechseltaste umfasst auch andere Eingabemethoden."</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Sprachwechsel unterdrücken"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Tasten-Pop-up"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Keine Verzögerung"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Immer anzeigen"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Im Hochformat anzeigen"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Nie anzeigen"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Taste für Einstellungen"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autokorrektur"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Korrektur fehlerhafter Wörter durch Leertaste und Satzzeichen"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Aus"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: gespeichert"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Los"</string>
     <string name="label_next_key" msgid="362972844525672568">"Weiter"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Vorh."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Fertig"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Senden"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Kein Text eingegeben"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tastencode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Umschalttaste"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Umschalttaste aktiviert"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Feststelltaste aktiviert"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Umschalttaste aktiviert (zum Deaktivieren berühren)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Feststelltaste aktiviert (zum Deaktivieren berühren)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Entf"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbole"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Buchstaben"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Spracheingabe"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Eingabe"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punkt"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Öffnende Klammer"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Schließende Klammer"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Doppelpunkt"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikolon"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Ausrufezeichen"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Fragezeichen"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Anführungszeichen"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Einfaches Anführungszeichen"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Aufzählungspunkt"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Quadratwurzel"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Trademark"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"c/o"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Sternchen"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Raute"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Auslassungszeichen"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Anführungszeichen unten"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Spracheingabe"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Spracheingaben werden zurzeit nicht für Ihre Sprache unterstützt, funktionieren jedoch in Englisch."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Die Spracheingabe verwendet die Spracherkennung von Google. Es gelten die "<a href="http://m.google.com/privacy">"Google Mobile-Datenschutzbestimmungen"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Rufen Sie zum Deaktivieren der Spracheingabe die Einstellungen für die Eingabemethode auf."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Drücken Sie zur Verwendung der Spracheingabe die Mikrofonschaltfläche."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Jetzt sprechen"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Vorgang läuft"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Fehler. Versuchen Sie es erneut.."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Keine Verbindung"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Fehler – Text zu lang"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Audio-Problem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Serverfehler"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Keine Sprache zu hören"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Keine Treffer gefunden"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Sprachsuche nicht installiert"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Hinweis:"</b>" Ziehen Sie zum Sprechen den Finger über die Tastatur."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Hinweis:"</b>" Versuchen Sie beim nächsten Mal, Satzzeichen wie \"Punkt\", \"Komma\" oder \"Fragezeichen\" per Sprachbefehl einzugeben."</string>
-    <string name="cancel" msgid="6830980399865683324">"Abbrechen"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Umschalttaste aktiviert"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Feststelltaste aktiviert"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Umschalttaste deaktiviert"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbolmodus"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Buchstabenmodus"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefonmodus"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefon-Symbolmodus"</string>
     <string name="voice_input" msgid="3583258583521397548">"Taste für Spracheingabe"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Auf Haupttastatur"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Auf Symboltastatur"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikro auf Haupttastatur"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikro auf Symboltastatur"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Spracheingabe deaktiviert"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Eingabemethode auswählen"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Eingabemethoden konfigurieren"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Eingabesprachen"</string>
     <string name="select_language" msgid="3693815588777926848">"Eingabesprachen"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Zum Speichern erneut berühren"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Zum Speichern erneut berühren"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Wörterbuch verfügbar"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Nutzer-Feedback aktivieren"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Tragen Sie zur Verbesserung dieses Eingabemethodeneditors bei, indem Sie automatisch Nutzungsstatistiken und Absturzberichte an Google senden."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturdesign"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Deutsche QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Englisch (Großbritannien)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Englisch (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modus der Studie zur Benutzerfreundlichkeit"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 6fe191e..da0cb36 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -21,12 +21,14 @@
 <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">"Πληκτρολόγιο Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Πληκτρολόγιο Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Ρυθμίσεις πληκτρολογίου Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Επιλογές εισόδου"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Διόρθωση Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Ορθογραφικός έλεγχος Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Ορθογραφικός έλεγχος Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Ρυθμίσεις ορθογραφικού ελέγχου"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Χρ. δεδ. εγγύτ."</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Χρησ. αλγόρ. εγγύτ. τύπου πληκτρ., για ορθ. έλεγχο"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Αναζήτηση ονομάτων επαφών"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Ο ορθογρ. έλεγχος χρησιμοπ. καταχωρίσεις από τη λίστα επαφών σας"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Δόνηση κατά το πάτημα πλήκτρων"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ήχος κατά το πάτημα πλήκτρων"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Εμφάνιση με το πάτημα πλήκτρου"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Διόρθωση κειμένου"</string>
     <string name="misc_category" msgid="6894192814868233453">"Άλλες επιλογές"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Σύνθετες ρυθμίσεις"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Επιλογές για έμπειρους χρήστες"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Επιλογές για έμπειρους χρήστες"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Άλλη μέθοδος εισόδου"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Το κλειδί αλλαγής γλώσσας καλύπτει και άλλες μεθόδους εισόδου"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Κατάργ. κλειδιού γλώσσας"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Χρόνος εξαφ. αναδ. παραθ."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Χωρίς καθυστέρ."</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Προεπιλογή"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Να εμφανίζεται πάντα"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Εμφάνιση σε λειτουργία κατακόρυφης προβολής"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Πάντα απόκρυψη"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Εμφάνιση πλήκτρου ρυθμίσεων"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Αυτόματη διόρθωση"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Τα πλήκτρα διαστήματος και στίξης διορθ. αυτόμ. λάθος λέξεις"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Απενεργοποίηση"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Αποθηκεύτηκε"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Μετ."</string>
     <string name="label_next_key" msgid="362972844525672568">"Επόμενο"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Προηγ"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Τέλος"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Αποστολή"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ΑΒΓ"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Δεν υπάρχει κείμενο"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Κωδικός πλήκτρου %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift ενεργοποιημένο"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock ενεργοποιημένο"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Το Shift είναι ενεργοποιημένο (πατήστε για απενεργοποίηση)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Το Caps lock είναι ενεργοποιημένο (πατήστε για απενεργοποίηση)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Πλήκτρο Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Σύμβολα"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Γράμματα:"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Μικρόφωνο"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Πλήκτρο Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Κόμμα"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Τελεία"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Αριστερή παρένθεση"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Δεξιά παρένθεση"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Άνω και κάτω τελεία"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Ερωτηματικό"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Θαυμαστικό"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Ερωτηματικό"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Διπλά εισαγωγικά"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Μονό εισαγωγικό"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Κουκκίδα"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Τετραγωνική ρίζα"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"πι"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Δέλτα"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Εμπορικό σήμα"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Υπεύθυνος"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Αστερίσκος"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Δίεση"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Αποσιωπητικά"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Χαμηλό διπλό εισαγωγικό"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Φωνητική είσοδος"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Η φωνητική είσοδος δεν υποστηρίζεται αυτή τη στιγμή για τη γλώσσα σας, ωστόσο λειτουργεί στα Αγγλικά."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Οι φωνητικές εντολές χρησιμοποιούν την τεχνολογία αναγνώρισης φωνής της Google. Ισχύει "<a href="http://m.google.com/privacy">"η Πολιτική Απορρήτου για κινητά"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Για να απενεργοποιήσετε τις φωνητικές εντολές, μεταβείτε στις ρυθμίσεις της μεθόδου εισαγωγής."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Για τη χρήση φωνητικών εντολών, πατήστε το κουμπί του μικροφώνου."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Μιλήστε τώρα"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Σε λειτουργία"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Σφάλμα. Δοκιμάστε ξανά."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Δεν ήταν δυνατή η σύνδεση"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Σφάλμα, πολλές λέξεις."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Πρόβλημα ήχου"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Σφάλμα διακομιστή"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Δεν ακούγεται ομιλία"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Δεν βρέθηκε καμία αντιστοίχιση"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Η Φωνητική αναζήτηση δεν εγκαταστάθηκε"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Υπόδειξη:"</b>" Σύρετε κατά μήκος του πληκτρολογίου για να μιλήσετε"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Υπόδειξη:"</b>" Την επόμενη φορά, προσπαθήστε να προφέρετε σημεία στίξης, όπως \"τελεία\", \"κόμμα\" ή \"ερωτηματικό\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Ακύρωση"</string>
-    <string name="ok" msgid="7898366843681727667">"ΟΚ"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Το Shift ενεργοποιημένο"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Το Caps lock είναι ενεργοποιημένο"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Το Shift είναι απενεργοποιημένο"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Λειτουργία συμβόλων"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Λειτουργία γραμμάτων"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Λειτουργία τηλεφώνου"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Λειτουργία συμβόλων τηλεφώνου"</string>
     <string name="voice_input" msgid="3583258583521397548">"Κλειδί φωνητικής εξόδου"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Στο κύριο πληκτρολ."</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Πληκτρ. συμβ. ενερ."</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Μικ. στο κύριο πληκ."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Μικ. στο πληκ. συμβ."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Απεν. φωνητ. είσοδος"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Επιλογή μεθόδου εισόδου"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Διαμόρφωση μεθόδων εισαγωγής"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Γλώσσες εισόδου"</string>
     <string name="select_language" msgid="3693815588777926848">"Γλώσσες εισόδου"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Αγγίξτε ξανά για αποθήκευση"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Αγγίξτε ξανά για αποθήκευση"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Λεξικό διαθέσιμο"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ενεργοποίηση σχολίων χρηστών"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Βοηθήστε μας να βελτιώσουμε αυτό το πρόγραμμα επεξεργασίας μεθόδου εισόδου στέλνοντας αυτόματα στατιστικά στοιχεία και αναφορές σφαλμάτων στην Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Θέμα πληκτρολογίου"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Γερμανικά QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Αγγλικά (Η.Β.)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Αγγλικά (Η.Π.Α)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Λειτουργία μελέτης χρηστικότητας"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index a7d5086..0aae21e 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android keyboard"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android keyboard settings"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android correction"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android spell checker"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android spell checker (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Spellchecking settings"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Use proximity data"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Use a keyboard-like proximity algorithm for spellchecking"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Spell checker uses entries from your contact list"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on key-press"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sound on key-press"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up on key press"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Text correction"</string>
     <string name="misc_category" msgid="6894192814868233453">"Other Options"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Advanced settings"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Options for expert users"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Options for experts"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Switch to other input methods"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Language switch key covers other input methods too"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Suppress language switch key"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Key pop-up dismiss delay"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"No delay"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Always show"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Show on portrait mode"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Always hide"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Show settings key"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Auto-correction"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacebar and punctuation correct mistyped words automatically"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Saved"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Go"</string>
     <string name="label_next_key" msgid="362972844525672568">"Next"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Prev"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Done"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Send"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No text entered"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Key code %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift enabled"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock enabled"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift on (tap to disable)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock on (tap to disable)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbols"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letters"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Voice input"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley face"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Comma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Full stop"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Left parenthesis"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Right parenthesis"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Colon"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semi-colon"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Exclamation mark"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Question mark"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Double quote"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Single quote"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Dot"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Square root"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Trademark"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Care of"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Star"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Pound"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Low double quote"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Voice input"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Voice input is not currently supported for your language, but does work in English."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Voice input uses Google\'s speech recognition. "<a href="http://m.google.com/privacy">"The Mobile Privacy Policy"</a>" applies."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"To turn off voice input, go to input method settings."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"To use voice input, press the microphone button."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Speak now"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Working"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Error: Please try again."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Couldn\'t connect"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Error, too much speech."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Audio problem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Server error"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"No speech heard"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"No matches found"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Voice search not installed"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Hint:"</b>" Swipe across keyboard to speak"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Hint:"</b>" Next time, try speaking punctuation marks, like \"full stop\", \"comma\" or \"question mark\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Cancel"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift enabled"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock enabled"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift disabled"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbols mode"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Letters mode"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Phone mode"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Phone symbols mode"</string>
     <string name="voice_input" msgid="3583258583521397548">"Voice input key"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"On main keyboard"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"On symbols keyboard"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic on main keyboard"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic on symbols keyboard"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Voice input is disabled"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Select input method"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configure input methods"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Input languages"</string>
     <string name="select_language" msgid="3693815588777926848">"Input languages"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Touch again to save"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionary available"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Enable user feedback"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Help improve this input method editor by sending usage statistics and crash reports automatically to Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Keyboard theme"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"German QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Usability study mode"</string>
diff --git a/java/res/values-en/donottranslate-more-keys.xml b/java/res/values-en/donottranslate-more-keys.xml
index bc26c6a..6e43e86 100644
--- a/java/res/values-en/donottranslate-more-keys.xml
+++ b/java/res/values-en/donottranslate-more-keys.xml
@@ -18,12 +18,46 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,á,â,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,è,é,ê,ë,ē</string>
-    <string name="more_keys_for_i">8,î,ï,í,ī,ì</string>
-    <string name="more_keys_for_o">9,ô,ö,ò,ó,œ,ø,ō,õ</string>
-    <string name="more_keys_for_s">ß</string>
-    <string name="more_keys_for_u">7,û,ü,ù,ú,ū</string>
-    <string name="more_keys_for_n">ñ</string>
-    <string name="more_keys_for_c">ç</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E1;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0103;</string>
+    <!-- U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE -->
+    <string name="more_keys_for_i">&#x00EE;,&#x00EF;,&#x00ED;,&#x012B;,&#x00EC;</string>
+    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
+    <string name="more_keys_for_o">&#x00F4;,&#x00F6;,&#x00F2;,&#x00F3;,&#x0153;,&#x00F8;&#x014D;,&#x00F5;</string>
+    <!-- U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FB;,&#x00FC;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S -->
+    <string name="more_keys_for_s">&#x00DF;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
+    <string name="more_keys_for_n">&#x00F1;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA -->
+    <string name="more_keys_for_c">&#x00E7;</string>
 </resources>
diff --git a/java/res/values-en/whitelist.xml b/java/res/values-en/whitelist.xml
index f929cec..fd79999 100644
--- a/java/res/values-en/whitelist.xml
+++ b/java/res/values-en/whitelist.xml
@@ -422,6 +422,10 @@
         <item>needn\'t</item>
 
         <item>255</item>
+        <item>nit</item>
+        <item>not</item>
+
+        <item>255</item>
         <item>oclock</item>
         <item>o\'clock</item>
 
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index b04a441..b9c4963 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -21,12 +21,14 @@
 <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">"Teclado de Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado de Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Configuración de teclado de Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones de entrada"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Corrector de Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corrector ortográfico de Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corrector ortográfico de Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Configuración del corrector ortográfico"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Utilizar datos de prox."</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Utilizar algoritmo de prox. de teclado para corrector ortográfico"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nombres contactos"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortográfico usa entradas de tu lista de contactos."</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Aviso emergente al pulsar tecla"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Corrección de texto"</string>
     <string name="misc_category" msgid="6894192814868233453">"Otras opciones"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Configuración avanzada"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opciones para usuarios expertos"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opciones para expertos"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Otros métodos de entrada"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"La tecla de cambio de idioma abarca otros métodos de entrada."</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Supr. tecla cambio idioma"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Retraso en rechazo de alerta de tecla"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sin demora"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminada"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar en modo retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar siempre"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla de configuración"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Corrección automática"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"La barra espaciadora y las teclas de puntuación insertan automáticamente la palabra corregida"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivado"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Siguiente"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Ant."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Hecho"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No se ingresó texto."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Clave de código %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Mayús habilitada"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Bloqueo de mayúsculas habilitado"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Se activó el modo Mayúscula (toca para desactivarlo)."</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Se activó el bloqueo de mayúsculas (toca para desactivarlo)."</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Borrar"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbolos"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letras"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Entrada de voz"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Carita sonriente"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Volver"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Coma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punto"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Paréntesis de apertura"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Paréntesis de cierre"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dos puntos"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Punto y coma"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Signo de admiración"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Signo de interrogación"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Comillas dobles"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Comillas simples"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punto"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Raíz cuadrada"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marca registrada"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"En atención de"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Destacar"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Numeral"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Elipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Comillas bajas"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Entrada por voz"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La entrada por voz no está admitida en tu idioma, pero sí funciona en inglés."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La entrada de voz usa el reconocimiento de voz de Google. "<a href="http://m.google.com/privacy">"Se aplica la política de privacidad para"</a>" celulares."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Para desactivar la entrada de voz, ve a la configuración de métodos de entrada."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Para utilizar entrada de voz, presiona el botón micrófono."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Habla ahora"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Procesando"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Error. Vuelve a intentarlo."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"No se pudo establecer la conexión."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Error, demasiado discurso."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema de audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Error del servidor"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"No se oyó la voz"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"No se encontraron coincidencias"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Búsqueda por voz no instalada"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Sugerencia:"</b>" Deslizar en el teclado para hablar"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Sugerencia:"</b>" La próxima vez intenta decir la puntuación como \"punto\", \"coma\" o \"signo de pregunta\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Cancelar"</string>
-    <string name="ok" msgid="7898366843681727667">"Aceptar"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Se activó el modo Mayúscula."</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Se activó el bloqueo de mayúsculas."</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Se desactivó el modo Mayúscula"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Modo Símbolos"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Modo Letras"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Modo Teléfono"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Modo Símbolos del teléfono"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tecla de entrada por voz"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"En el teclado principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"En el teclado de símbolos"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micrófono en el teclado principal"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micrófono en el teclado de símbolos"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"La entrada por voz está inhabilitada"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Seleccionar método de entrada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas de entrada"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tocar de nuevo para guardar"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionario disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activar los comentarios del usuario"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Ayuda a mejorar este editor de método de introducción de texto al enviar las estadísticas de uso y los informes de error a Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema del teclado"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY alemán"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglés (Reino Unido)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglés (EE.UU.)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudio de usabilidad"</string>
diff --git a/java/res/values-es/donottranslate-more-keys.xml b/java/res/values-es/donottranslate-more-keys.xml
index d5a8ed1..f56b1d5 100644
--- a/java/res/values-es/donottranslate-more-keys.xml
+++ b/java/res/values-es/donottranslate-more-keys.xml
@@ -18,12 +18,56 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">á,à,ä,â,ã,å,ą,æ,ā,ª</string>
-    <string name="more_keys_for_e">3,é,è,ë,ê,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,í,ï,ì,î,į,ī</string>
-    <string name="more_keys_for_o">9,ó,ò,ö,ô,õ,ø,œ,ō,º</string>
-    <string name="more_keys_for_u">7,ú,ü,ù,û,ū</string>
-    <string name="more_keys_for_n">ñ,ń</string>
-    <string name="more_keys_for_c">ç,ć,č</string>
-    <string name="more_keys_for_punctuation">"\\,,\?,!,¿,¡,:,-,\',\",),(,/,;,+,&amp;,\@"</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E0;,&#x00E4;,&#x00E2;,&#x00E3;,&#x00E5;,&#x0105;,&#x00E6;,&#x0101;,&#x00AA;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00E8;,&#x00EB;,&#x00EA;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EF;,&#x00EC;,&#x00EE;,&#x012F;,&#x012B;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F2;,&#x00F6;,&#x00F4;,&#x00F5;,&#x00F8;,&#x0153;,&#x014D;,&#x00BA;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x00F9;,&#x00FB;,&#x016B;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+    <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK
+         U+00BF: "¿" INVERTED QUESTION MARK -->
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!7,#,-,&#x00A1;,!,&#x00BF;,\\,,\?,\\%,+,;,:,/,(,),\@,&amp;,\",\'"</string>
 </resources>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index fcd65e1..a746c37 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -21,12 +21,14 @@
 <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">"Teclado de Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Ajustes del teclado de Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones introducción texto"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Corrector de Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corrector ortográfico de Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corrector ortográfico de Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Ajustes del corrector ortográfico"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Usar datos de proximidad"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Usar algoritmo de proximidad de teclado para corregir la ortografía"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nombres de contactos"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortográfico usa entradas de tu lista de contactos."</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up al pulsar tecla"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Corrección ortográfica"</string>
     <string name="misc_category" msgid="6894192814868233453">"Otras opciones"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Ajustes avanzados"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opciones para usuarios expertos"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opciones para expertos"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Otros métodos de introducción"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"La tecla de cambio de idioma sirve también para otros métodos."</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Eliminar tecla cambiar idioma"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Retraso de rechazo"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sin retraso"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminado"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar en modo vertical"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar siempre"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla de ajustes"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autocorrección"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Pulsar la tecla de espacio o punto para corregir errores"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivada"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Sig."</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Anterior"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Ok"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No se ha introducido texto."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Código del teclado: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Tecla Mayús habilitada"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Bloq Mayús habilitado"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Mayúsculas activadas (tocar para inhabilitar)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Bloqueo de mayúsculas activado (tocar para inhabilitar)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Suprimir"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbolos"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letras"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Entrada de voz"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Emoticono"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Tecla Intro"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Coma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punto"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Paréntesis de apertura"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Paréntesis de cierre"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dos puntos"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Punto y coma"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Signo de exclamación"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Signo de interrogación"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Comillas dobles"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Comillas simples"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punto"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Raíz cuadrada"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marca comercial"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Porcentaje"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Asterisco"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Almohadilla"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Puntos suspensivos"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Comillas dobles bajas"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Introducción de voz"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualmente la introducción de voz no está disponible en tu idioma, pero se puede utilizar en inglés."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La entrada de voz utiliza el reconocimiento de voz de Google. Se aplica la "<a href="http://m.google.com/privacy">"Política de privacidad de Google para móviles"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Para desactivar la función de entrada de voz, accede a los ajustes del método de introducción de texto."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Para utilizar la entrada de voz, pulsa el botón de micrófono."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Habla ahora"</string>
-    <string name="voice_working" msgid="6666937792815731889">"En curso"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Se ha producido un error. Inténtalo de nuevo."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"No se ha podido establecer conexión."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Se ha producido un error debido a un exceso de introducción de datos de voz."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema de audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Error del servidor"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Ninguna conversación escuchada"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"No se ha encontrado ninguna coincidencia."</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"La búsqueda por voz no está instalada."</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Sugerencia:"</b>" muévete por el teclado para hablar."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Sugerencia:"</b>" la próxima vez, prueba a indicar signos de puntuación como, por ejemplo, \"punto\", \"coma\" o \"signo de interrogación\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Cancelar"</string>
-    <string name="ok" msgid="7898366843681727667">"Aceptar"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Mayúsculas habilitadas"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Bloqueo de mayúsculas habilitado"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Mayúsculas inhabilitadas"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Modo de símbolos"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Modo de letras"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Modo de teléfono"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Modo de símbolos de teléfono"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tecla de entrada de voz"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"En teclado principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"En teclado de símbolos"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micrófono en teclado principal"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micro en teclado de símbolos"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entrada de voz inhabilitada"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Selecciona un método de introducción de texto"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de introducción"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Volver a tocar para guardar"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toca otra vez para guardar."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Hay un diccionario disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Habilitar comentarios de usuarios"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Ayuda a mejorar este editor de método de introducción de texto enviando estadísticas de uso e informes de error a Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema de teclado"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY alemán"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"inglés (Reino Unido)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"inglés (EE.UU.)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudio de usabilidad"</string>
diff --git a/java/res/values-et/donottranslate-more-keys.xml b/java/res/values-et/donottranslate-more-keys.xml
new file mode 100644
index 0000000..69cf654
--- /dev/null
+++ b/java/res/values-et/donottranslate-more-keys.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK -->
+    <string name="more_keys_for_a">&#x00E4;,&#x0101;,&#x00E0;,&#x00E1;,&#x00E2;,&#x00E3;,&#x00E5;,&#x00E6;,&#x0105;</string>
+    <!-- U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+011B: "ě" LATIN SMALL LETTER E WITH CARON -->
+    <string name="more_keys_for_e">&#x0113;,&#x00E8;,&#x0117;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0119;,&#x011B;</string>
+    <!-- U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+0131: "ı" LATIN SMALL LETTER DOTLESS I -->
+    <string name="more_keys_for_i">&#x012B;,&#x00EC;,&#x012F;,&#x00ED;,&#x00EE;,&#x00EF;,&#x0131;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_o">&#x00F6;,&#x00F5;,&#x00F2;,&#x00F3;,&#x00F4;,&#x0153;,&#x0151;,&#x00F8;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+         U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+         U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE -->
+    <string name="more_keys_for_u">&#x00FC;,&#x016B;,&#x0173;,&#x00F9;,&#x00FA;,&#x00FB;,&#x016F;,&#x0171;</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA -->
+    <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;,&#x015F;</string>
+    <!-- U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+         U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x0146;,&#x00F1;,&#x0144;,&#x0144;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x010D;,&#x00E7;,&#x0107;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+010F: "ď" LATIN SMALL LETTER D WITH CARON -->
+    <string name="more_keys_for_d">&#x010F;</string>
+    <!-- U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+         U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+         U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE -->
+    <string name="more_keys_for_r">&#x0157;,&#x0159;,&#x0155;</string>
+    <!-- U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+         U+0165: "ť" LATIN SMALL LETTER T WITH CARON -->
+    <string name="more_keys_for_t">&#x0163;,&#x0165;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017C;,&#x017A;</string>
+    <!-- U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA -->
+    <string name="more_keys_for_k">&#x0137;</string>
+    <!-- U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+         U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+         U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+         U+013E: "ľ" LATIN SMALL LETTER L WITH CARON -->
+    <string name="more_keys_for_l">&#x013C;,&#x0142;,&#x013A;,&#x013E;</string>
+    <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+         U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row1_11">&#x00FC;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row2_11">&#x00E4;</string>
+    <!-- U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
+    <string name="more_keys_for_nordic_row2_10">&#x00F5;</string>
+</resources>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 2c69c99..1c7f14e 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -21,12 +21,14 @@
 <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">"Androidi klaviatuur"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-klaviatuur (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Androidi klaviatuuriseaded"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Sisestusvalikud"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Androidi parandus"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidi õigekirjakontroll"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidi õigekirjakontroll (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Õigekirjakontrolli seaded"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Kasuta lähedusandmeid"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Kasuta õigekirjakontrollis klaviatuurisarnast lähedusalgoritmi"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontakti nimede kontroll."</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Õigekirjakontroll kasutab teie kontaktisikute loendi sissekandeid"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreeri klahvivajutusel"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Heli klahvivajutusel"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Klahvivajutusel kuva hüpik"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Teksti parandamine"</string>
     <string name="misc_category" msgid="6894192814868233453">"Muud valikud"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Täpsemad seaded"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Valikud ekspertkasutajatele"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Valikud ekspertidele"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Vaheta sisestusmeetodit"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Keelevahetuse võti hõlmab ka muid sisestusmeetodeid"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Keela keelevahetuse võti"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Hüpiku loobumisviivitus"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Viivituseta"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Vaikeseade"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Kuva alati"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Kuva portreerežiimis"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Peida alati"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Kuva seadete võti"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automaatparandus"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Tühik ja kirjavahemärgid parand. autom. kirjavigadega sõnad"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Väljas"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : salvestatud"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Mine"</string>
     <string name="label_next_key" msgid="362972844525672568">"Edasi"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Eelm."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Valmis"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Saada"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Teksti ei ole sisestatud"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Klahvi kood: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Tõstuklahv"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Tõstuklahv on lubatud"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Suurtähelukk on lubatud"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tõstuklahv sees (puudutage keelamiseks)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Suurtähelukk on sees (puudutage keelamiseks)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Kustuta"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Sümbolid"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Tähed"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Kõnesisend"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Naerunägu"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Tagasi"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Koma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punkt"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Vasaksulg"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Paremsulg"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Koolon"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikoolon"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Hüüumärk"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Küsimärk"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Jutumärgid"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Üksikjutumärgid"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punkt"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Ruutjuur"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pii"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Kaubamärk"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Vahendaja"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Tärn"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Nael"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Kolmikpunkt"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Alumised jutumärgid"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Kõnesisend"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Kõnesisendit ei toetata praegu teie keeles, kuid see töötab inglise keeles."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Kõnesisend kasutab Google\'i kõnetuvastust. Kehtivad "<a href="http://m.google.com/privacy">"Mobile\'i privaatsuseeskirjad"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Kõnesisendi väljalülitamiseks minge sisestusmeetodi seadete juurde."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Kõnesisendi kasutamiseks vajutage mikrofoni nuppu."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Alustage rääkimist"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Töötab"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Viga. Proovige uuesti."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Ühendamine nurjus."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Viga. Liiga palju kõnet."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Heli probleem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Serveri viga"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Kõne pole kuuldav"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Ühtki vastet ei leitud"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Hääleotsing pole installitud"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Vihje:"</b>" rääkimiseks libistage sõrme üle ekraani"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Vihje:"</b>" proovige järgmine kord kirjavahemärkide ütlemist, nt „punkt”, „koma” või „küsimärk”."</string>
-    <string name="cancel" msgid="6830980399865683324">"Tühista"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Tõstuklahv on lubatud"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Suurtähelukk on lubatud"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Tõstuklahv on keelatud"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Sümbolite režiim"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Tähtede režiim"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefonirežiim"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefoni sümbolite režiim"</string>
     <string name="voice_input" msgid="3583258583521397548">"Häälesisendi klahv"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Peamisel klaviatuuril"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Sümbol. klaviatuuril"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr. peam. klaviat."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. sümb. klaviat."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Kõnesisend on keelatud"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Sisestusmeet. valim."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Sisestusmeetodite seadistamine"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Sisestuskeeled"</string>
     <string name="select_language" msgid="3693815588777926848">"Sisestuskeeled"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"←Salvestamiseks puudutage uuesti"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Sõnastik saadaval"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Luba kasutaja tagasiside"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Saatke Google\'ile automaatselt kasutusstatistikat ja krahhiaruandeid ning aidake seda sisestusmeetodi redigeerijat parandada."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Klaviatuuri teema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Saksa QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglise (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglise (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Kasutatavuse uurimisrežiim"</string>
diff --git a/java/res/values-fa/donottranslate-more-keys.xml b/java/res/values-fa/donottranslate-more-keys.xml
new file mode 100644
index 0000000..1fb1846
--- /dev/null
+++ b/java/res/values-fa/donottranslate-more-keys.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+061F: "؟" ARABIC QUESTION MARK
+         U+060C: "،" ARABIC COMMA
+         U+061B: "؛" ARABIC SEMICOLON -->
+    <!-- U+0650: "ِ" ARABIC KASRA
+         U+064E: "َ" ARABIC FATHA
+         U+064D: "ٍ" ARABIC KASRATAN
+         U+064B: "ً" ARABIC FATHATAN
+         U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+         U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+         U+0655: "ٕ" ARABIC HAMZA BELOW
+         U+0654: "ٔ" ARABIC HAMZA ABOVE -->
+    <!-- U+064F: "ُ" ARABIC DAMMA
+         U+064C: "ٌ" ARABIC DAMMATAN
+         U+0651: "ّ" ARABIC SHADDA
+         U+0652: "ْ" ARABIC SUKUN
+         U+0653: "ٓ" ARABIC MADDAH ABOVE
+         U+0640: "ـ" ARABIC TATWEEL -->
+    <!-- In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. -->
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',-,:,!,&#x061F;,&#x060C;,&#x061B;,&#x0650;,&#x064E;,&#x064D;,&#x064B;,&#x0656;,&#x0670;,&#x0655;,&#x0654;,&#x064F;,&#x064C;,&#x0651;,&#x0652;,&#x0653;,&#x0640;&#x0640;&#x0640;|&#x0640;,/"</string>
+    <string name="keyhintlabel_for_punctuation">&#x064B;</string>
+    <!-- U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE -->
+    <string name="keylabel_for_symbols_1">&#x06F1;</string>
+    <!-- U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO -->
+    <string name="keylabel_for_symbols_2">&#x06F2;</string>
+    <!-- U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE -->
+    <string name="keylabel_for_symbols_3">&#x06F3;</string>
+    <!-- U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR -->
+    <string name="keylabel_for_symbols_4">&#x06F4;</string>
+    <!-- U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE -->
+    <string name="keylabel_for_symbols_5">&#x06F5;</string>
+    <!-- U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
+    <string name="keylabel_for_symbols_6">&#x06F6;</string>
+    <!-- U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+    <string name="keylabel_for_symbols_7">&#x06F7;</string>
+    <!-- U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
+    <string name="keylabel_for_symbols_8">&#x06F8;</string>
+    <!-- U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE -->
+    <string name="keylabel_for_symbols_9">&#x06F9;</string>
+    <!-- U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO -->
+    <string name="keylabel_for_symbols_0">&#x06F0;</string>
+    <string name="additional_more_keys_for_symbols_1">1</string>
+    <string name="additional_more_keys_for_symbols_2">2</string>
+    <string name="additional_more_keys_for_symbols_3">3</string>
+    <string name="additional_more_keys_for_symbols_4">4</string>
+    <string name="additional_more_keys_for_symbols_5">5</string>
+    <string name="additional_more_keys_for_symbols_6">6</string>
+    <string name="additional_more_keys_for_symbols_7">7</string>
+    <string name="additional_more_keys_for_symbols_8">8</string>
+    <string name="additional_more_keys_for_symbols_9">9</string>
+    <!-- U+066B: "٫" ARABIC DECIMAL SEPARATOR
+         U+066C: "٬" ARABIC THOUSANDS SEPARATOR -->
+    <string name="additional_more_keys_for_symbols_0">0,&#x066B;,&#x066C;</string>
+    <!-- U+060C: "،" ARABIC COMMA -->
+    <string name="keylabel_for_comma">&#x060C;</string>
+    <string name="more_keys_for_comma">"\\,"</string>
+    <string name="keylabel_for_symbols_question">&#x061F;</string>
+    <string name="keylabel_for_symbols_semicolon">&#x061B;</string>
+    <!-- U+066A: "٪" ARABIC PERCENT SIGN -->
+    <string name="keylabel_for_symbols_percent">&#x066A;</string>
+    <string name="more_keys_for_symbols_question">\?</string>
+    <string name="more_keys_for_symbols_semicolon">;</string>
+    <!-- U+2030: "‰" PER MILLE SIGN -->
+    <string name="more_keys_for_symbols_percent">%,&#x2030;</string>
+    <!-- U+060C: "،" ARABIC COMMA
+         U+061B: "؛" ARABIC SEMICOLON
+         U+061F: "؟" ARABIC QUESTION MARK -->
+    <string name="keylabel_for_tablet_comma">"&#x060C;"</string>
+    <string name="keyhintlabel_for_tablet_comma">"!"</string>
+    <string name="more_keys_for_tablet_comma">"!,\\,"</string>
+    <string name="keyhintlabel_for_tablet_period">"&#x061F;"</string>
+    <string name="more_keys_for_tablet_period">"&#x061F;,\?"</string>
+    <string name="keyhintlabel_for_dash">&#x064B;</string>
+    <string name="more_keys_for_apostrophe">"&#x061F;,&#x061B;,!,:,-,/,\',\""</string>
+    <!-- U+0651: "ّ" ARABIC SHADDA
+         U+0652: "ْ" ARABIC SUKUN
+         U+064C: "ٌ" ARABIC DAMMATAN
+         U+0653: "ٓ" ARABIC MADDAH ABOVE
+         U+064F: "ُ" ARABIC DAMMA -->
+    <!-- U+0650: "ِ" ARABIC KASRA
+         U+064E: "َ" ARABIC FATHA
+         U+064B: "ً" ARABIC FATHATAN
+         U+0640: "ـ" ARABIC TATWEEL
+         U+064D: "ٍ" ARABIC KASRATAN -->
+    <!-- U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+         U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+         U+0654: "ٔ" ARABIC HAMZA ABOVE
+         U+0655: "ٕ" ARABIC HAMZA BELOW -->
+    <!-- In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. -->
+    <string name="more_keys_for_dash">"&#x0651;,&#x0652;,&#x064C;,&#x0653;,&#x064F;,&#x0650;,&#x064E;,&#x064B;,&#x0640;&#x0640;&#x0640;|&#x0640;,&#x064D;,&#x0654;,&#x0656;,&#x0655;,_,&#x0670;"</string>
+    <!-- U+266A: "♪" EIGHTH NOTE -->
+    <string name="more_keys_for_bullet">&#x266A;</string>
+    <!-- U+2605: "★" BLACK STAR
+         U+066D: "٭" ARABIC FIVE POINTED STAR -->
+    <string name="more_keys_for_star">&#x2605;,&#x066D;</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- U+0029: ")" RIGHT PARENTHESIS -->
+    <integer name="keycode_for_left_parenthesis">0x0029</integer>
+    <!-- U+0028: "(" LEFT PARENTHESIS -->
+    <integer name="keycode_for_right_parenthesis">0x0028</integer>
+    <!-- U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
+         U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+FD3E ORNATE LEFT PARENTHESIS -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+FD3F ORNATE RIGHT PARENTHESIS -->
+    <string name="more_keys_for_left_parenthesis">!fixedColumnOrder!4,&#xFD3E;|&#xFD3F;,&lt;|&gt;,{|},[|]</string>
+    <string name="more_keys_for_right_parenthesis">!fixedColumnOrder!4,&#xFD3F;|&#xFD3E;,&gt;|&lt;,}|{,]|[</string>
+    <!-- U+003E: ">" GREATER-THAN SIGN -->
+    <integer name="keycode_for_less_than">0x003E</integer>
+    <!-- U+003C: "<" LESS-THAN SIGN -->
+    <integer name="keycode_for_greater_than">0x003C</integer>
+    <!-- U+2264: "≤" LESS-THAN OR EQUAL TO
+         U+2265: "≥" GREATER-THAN EQUAL TO
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+         The following characters don't need BIDI mirroring.
+         U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
+    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
+    <!-- U+005D: "]" RIGHT SQUARE BRACKET -->
+    <integer name="keycode_for_left_square_bracket">0x005D</integer>
+    <!-- U+005B: "[" LEFT SQUARE BRACKET -->
+    <integer name="keycode_for_right_square_bracket">0x005B</integer>
+    <!-- U+007D: "}" RIGHT CURLY BRACKET -->
+    <integer name="keycode_for_left_curly_bracket">0x007D</integer>
+    <!-- U+007B: "{" LEFT CURLY BRACKET -->
+    <integer name="keycode_for_right_curly_bracket">0x007B</integer>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+</resources>
diff --git a/java/res/values-fr-rCH/donottranslate-more-keys.xml b/java/res/values-fa/donottranslate.xml
similarity index 68%
copy from java/res/values-fr-rCH/donottranslate-more-keys.xml
copy to java/res/values-fa/donottranslate.xml
index 561c5e5..57de253 100644
--- a/java/res/values-fr-rCH/donottranslate-more-keys.xml
+++ b/java/res/values-fa/donottranslate.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,9 +18,8 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_y">ÿ</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z">6</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- Symbols that are suggested between words -->
+    <string name="suggested_punctuations">!,?,\\,,:,;,\",(|),)|(,\',-,/,@,_</string>
 </resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 7d8c1e9..fb3654e 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -21,12 +21,14 @@
 <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">"صفحه کلید Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"صفحه کلید (Android (AOSP"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"تنظیمات صفحه کلید Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"گزینه های ورودی"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"تصحیح Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"غلط‌گیر املای Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"غلط‌گیر املای Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"تنظیمات غلط گیری املایی"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"استفاده از داده‌های مجاورت"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"استفاده از یک الگوریتم مجاورت مشابه صفحه کلید برای غلط گیری املایی"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"جستجوی نام مخاطبین"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"غلط‌گیر املا از ورودی‌های لیست مخاطبین شما استفاده میکند"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"لرزش با فشار کلید"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"صدا با فشار کلید"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"بازشدن با فشار کلید"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"تصحیح متن"</string>
     <string name="misc_category" msgid="6894192814868233453">"سایر گزینه ها"</string>
     <string name="advanced_settings" msgid="362895144495591463">"تنظیمات پیشرفته"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"گزینه هایی برای کاربران حرفه ای"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"گزینه‌هایی برای حرفه‌ای‌ها"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"تغییر به دیگر روشهای ورودی"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"کلید تغییر زبان، سایر ورودیهای زبان را نیز پوشش می‌دهد"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"کلید تغییر زبان را فشار دهید"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"تأخیر در رد کردن کلید نمایشی"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"بدون تأخیر"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"پیش فرض"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"همیشه نمایش داده شود"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"نمایش در حالت عمودی"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"همیشه پنهان شود"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"نمایش کلید تنظیمات"</string>
     <string name="auto_correction" msgid="4979925752001319458">"تصحیح خودکار"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"کلید فاصله و علائم نگارشی به صورت خودکار کلماتی را که غلط تایپ شده اند تصحیح می کنند"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"خاموش"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : ذخیره شد"</string>
     <string name="label_go_key" msgid="1635148082137219148">"برو"</string>
     <string name="label_next_key" msgid="362972844525672568">"بعدی"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"قبلی"</string>
     <string name="label_done_key" msgid="2441578748772529288">"انجام شد"</string>
     <string name="label_send_key" msgid="2815056534433717444">"ارسال"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -80,8 +85,8 @@
     <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
     <skip />
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift فعال است"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock فعال شد"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift فعال است (برای غیرفعال کردن ضربه بزنید)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock روشن است (برای غیرفعال کردن ضربه بزنید)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"نمادها"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"حروف"</string>
@@ -92,46 +97,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"ورودی صدا"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"صورت متبسم"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"کاما"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"نقطه"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"پرانتز چپ"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"پرانتز راست"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"دو نقطه"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"نقطه ویرگول"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"علامت تعجب"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"علامت سؤال"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"علامت نقل قول"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"علامت نقل قول تکی"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"نقطه"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"ریشه دوم"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"پی"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"دلتا"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"علامت تجاری"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"توسط"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"ستاره"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"پوند"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"سه نقطه"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"علامت نقل قول پایین"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"ورودی صوتی"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ورودی صوتی در حال حاضر برای زبان شما پشتیبانی نمی شود اما برای زبان انگلیسی فعال است."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ورودی صوتی از تشخیص صدای Google استفاده می کند. "<a href="http://m.google.com/privacy">"خط مشی رازداری Mobile "</a>" اعمال می شود."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"برای خاموش کردن ورودی صدا، به تنظیمات روش ورودی بروید."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"برای استفاده از ورودی صوتی، دکمه میکروفن را فشار دهید."</string>
-    <string name="voice_listening" msgid="467518160751321844">"اکنون صحبت کنید"</string>
-    <string name="voice_working" msgid="6666937792815731889">"در حال کار"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"خطا: لطفاً دوباره امتحان کنید."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"متصل نشد"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"خطا، گفتار بسیار زیاد است."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"مشکل صوتی"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"خطای سرور"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"گفتاری شنیده نشد"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"مورد منطبقی یافت نشد"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"جستجوی صوتی نصب نشده است"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"نکته: "</b>" برای صحبت روی صفحه کلید ضربه بزنید"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"نکته: دفعه دیگر، از نشانه گذاری های گفتاری مانند \"نقطه\"، \"کاما\" یا \"علامت سؤال\" استفاده کنید."</b></string>
-    <string name="cancel" msgid="6830980399865683324">"لغو"</string>
-    <string name="ok" msgid="7898366843681727667">"تأیید"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift فعال است"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock فعال شد"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift غیرفعال است"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"حالت نمادها"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"حالت حروف"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"حالت تلفن"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"حالت نمادهای تلفن"</string>
     <string name="voice_input" msgid="3583258583521397548">"کلید ورودی صدا"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"در صفحه کلید اصلی"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"در صفحه کلید نمادها"</string>
@@ -139,16 +112,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"میکروفن در صفحه کلید اصلی"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"میکروفن در صفحه کلید نمادها"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ورودی صدا غیرفعال است"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"انتخاب روش ورودی"</string>
     <string name="configure_input_method" msgid="373356270290742459">"پیکربندی روش های ورودی"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"زبان های ورودی"</string>
     <string name="select_language" msgid="3693815588777926848">"زبان‌های ورودی"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← جهت ذخیره دوباره لمس کنید"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"دیکشنری موجود است"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"فعال کردن بازخورد کاربر"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"با ارسال خودکار آمارهای کاربرد و گزارش های خرابی به Google، به بهبود این ویرایشگر روش ورودی کمک کنید."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"طرح زمینه صفحه کلید"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY آلمانی"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"انگیسی (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"انگیسی (US)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"حالت بررسی قابلیت استفاده"</string>
diff --git a/java/res/values-fi/donottranslate-more-keys.xml b/java/res/values-fi/donottranslate-more-keys.xml
index df67c69..25b7858 100644
--- a/java/res/values-fi/donottranslate-more-keys.xml
+++ b/java/res/values-fi/donottranslate-more-keys.xml
@@ -18,13 +18,39 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">æ,à,á,â,ã,ā</string>
-    <string name="more_keys_for_o">9,ø,ô,ò,ó,õ,œ,ō</string>
-    <string name="more_keys_for_u">7,ü</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
-    <string name="more_keys_for_z">ž,ź,ż</string>
-    <string name="keylabel_for_scandinavia_row2_10">ö</string>
-    <string name="keylabel_for_scandinavia_row2_11">ä</string>
-    <string name="more_keys_for_scandinavia_row2_10">ø</string>
-    <string name="more_keys_for_scandinavia_row2_11">æ</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E6;,&#x00E0;,&#x00E1;,&#x00E2;,&#x00E3;,&#x0101;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F8;,&#x00F4;,&#x00F2;,&#x00F3;,&#x00F5;,&#x0153;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS -->
+    <string name="more_keys_for_u">&#x00FC;</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE -->
+    <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017A;,&#x017C;</string>
+    <!-- U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE -->
+    <string name="keylabel_for_nordic_row1_11">&#x00E5;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row2_11">&#x00E4;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_nordic_row2_10">&#x00F8;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="more_keys_for_nordic_row2_11">&#x00E6;</string>
 </resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index d7c3199..7748178 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android-näppäimistö"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-näppäimistö (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android-näppäimistön asetukset"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Syöttövalinnat"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-korjaus"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-oikoluku"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-oikoluku (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Oikoluvun asetukset"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Käytä lähestymistietoja"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Käytä näppäimistön kaltaista lähestymisalgoritmia oikolukuun"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Hae kontaktien nimiä"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Oikeinkirjoituksen tarkistus käyttää kontaktiluettelosi tietoja."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Käytä värinää näppäimiä painettaessa"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Toista ääni näppäimiä painettaessa"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ponnahdusikkuna painalluksella"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Tekstin korjaus"</string>
     <string name="misc_category" msgid="6894192814868233453">"Muut vaihtoehdot"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Lisäasetukset"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Valinnat kokeneille käyttäjille"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Valinnat asiantuntijoille"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Käytä toista syöttötapaa"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Kielenvaihtonäppäin kattaa myös muut syöttötavat"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Piilota kielenvaihtonäpp."</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Näppäimen hylkäysviive"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Ei viivettä"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Oletus"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Näytä aina"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Näytä pystysuunnassa"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Piilota aina"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Näytä asetukset-näppäin"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autom. korjaus"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Välilyönnit ja välimerkit korjaavat väärinkirjoitetut sanat automaattisesti"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Älä käytä"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Tallennettu"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Siirry"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seur."</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Edell"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Valmis"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Lähetä"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ei kirjoitettua tekstiä"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Näppäimen koodi %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift päällä"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock päällä"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Vaihto päällä (napauta poistaaksesi käytöstä)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock päällä (napauta poistaaksesi käytöstä)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Poisto"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbolit"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Kirjaimet"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Puheohjaus"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Hymiö"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Pilkku"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Piste"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Vasen sulkumerkki"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Oikea sulkumerkki"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Kaksoispiste"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Puolipiste"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Huutomerkki"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Kysymysmerkki"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Lainausmerkki"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Puolilainausmerkki"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Piste"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Neliöjuuri"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pii"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Tavaramerkki"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"C/O"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Tähti"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Punta"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipsi"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Rivinalinen lainausmerkki"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Äänisyöte"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Äänisyötettä ei vielä tueta kielelläsi, mutta voit käyttää sitä englanniksi."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Äänisyöte käyttää Googlen puheentunnistusta. "<a href="http://m.google.com/privacy">"Mobile-tietosuojakäytäntö"</a>" on voimassa."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Siirry syöttöasetuksiin poistaaksesi äänisyötteen käytöstä."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Ota äänisyöte käyttöön painamalla mikrofonikuvaketta."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Puhu nyt"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Työstetään"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Virhe. Yritä uudelleen."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Ei yhteyttä"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Virhe, liikaa puhetta."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Ääniongelma"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Palvelinvirhe"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Puhetta ei kuulu"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Ei vastineita"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Puhehakua ei asennettu"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Vihje:"</b>" liu\'uta sormea näppäimistöllä ja puhu"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Vihje:"</b>" kokeile seuraavalla kerralla puhua välimerkit, kuten \"period\" (piste), \"comma\" (pilkku) tai \"question mark\" (kysymysmerkki)."</string>
-    <string name="cancel" msgid="6830980399865683324">"Peruuta"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Vaihto päällä"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock päällä"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Vaihto pois käytöstä"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbolit-tila"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Näppäimistötila"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Puhelintila"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Puhelinsymbolit-tila"</string>
     <string name="voice_input" msgid="3583258583521397548">"Ääniohjausavain"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Päänäppäimistössä"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Symbolinäppäimistössä"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr. päänäppäim."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. symbolinäppäim."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Ääniohjaus on pois käytöstä"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Valitse syöttötapa"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Määritä syöttötavat"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Syöttökielet"</string>
     <string name="select_language" msgid="3693815588777926848">"Syöttökielet"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tallenna koskettamalla uudelleen"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Sanakirja saatavilla"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ota käyttäjäpalaute käyttöön"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Auta parantamaan tätä syöttötavan muokkausohjelmaa lähettämällä automaattisesti käyttötietoja ja kaatumisraportteja Googlelle."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Näppäimistöteema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"saksa, QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"englanti (Iso-Britannia)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"englanti (Yhdysvallat)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Käytettävyystutkimustila"</string>
diff --git a/java/res/values-de-rZZ/donottranslate-more-keys.xml b/java/res/values-fr/donottranslate-config.xml
similarity index 74%
rename from java/res/values-de-rZZ/donottranslate-more-keys.xml
rename to java/res/values-fr/donottranslate-config.xml
index e7ec5e1..1f446d5 100644
--- a/java/res/values-de-rZZ/donottranslate-more-keys.xml
+++ b/java/res/values-fr/donottranslate-config.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_y">6</string>
-    <string name="more_keys_for_z"></string>
+
+<resources>
+    <bool name="config_require_ligatures_processing">true</bool>
 </resources>
diff --git a/java/res/values-fr/donottranslate-more-keys.xml b/java/res/values-fr/donottranslate-more-keys.xml
index cd6d49b..7b11a18 100644
--- a/java/res/values-fr/donottranslate-more-keys.xml
+++ b/java/res/values-fr/donottranslate-more-keys.xml
@@ -18,14 +18,51 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,1,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_e">é,è,ê,ë,3,ę,ė,ē</string>
-    <string name="more_keys_for_i">î,8,ï,ì,í,į,ī</string>
-    <string name="more_keys_for_o">ô,œ,9,ö,ò,ó,õ,ø,ō,º</string>
-    <string name="more_keys_for_u">ù,û,7,ü,ú,ū</string>
-    <string name="more_keys_for_c">ç,ć,č</string>
-    <string name="more_keys_for_y">6,ÿ</string>
-    <string name="more_keys_for_q"></string>
-    <string name="more_keys_for_w"></string>
-    <string name="more_keys_for_z">2</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E2;,%,&#x00E6;,&#x00E1;,&#x00E4;,&#x00E3;,&#x00E5;,&#x0101;,&#x00AA;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,%,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00EE;,%,&#x00EF;,&#x00EC;,&#x00ED;,&#x012F;,&#x012B;</string>
+    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+    <string name="more_keys_for_o">&#x00F4;,&#x0153;,%,&#x00F6;,&#x00F2;,&#x00F3;,&#x00F5;,&#x00F8;,&#x014D;,&#x00BA;</string>
+    <!-- U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00F9;,&#x00FB;,%,&#x00FC;,&#x00FA;,&#x016B;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+    <!-- U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">%,&#x00FF;</string>
 </resources>
diff --git a/java/res/values-fr/donottranslate.xml b/java/res/values-fr/donottranslate.xml
index 695750f..8cf2516 100644
--- a/java/res/values-fr/donottranslate.xml
+++ b/java/res/values-fr/donottranslate.xml
@@ -19,11 +19,11 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Symbols that should be swapped with a magic space -->
-    <string name="magic_space_swapping_symbols">.,\u0022)]}</string>
+    <string name="weak_space_swapping_symbols">.,\")]}</string>
     <!-- Symbols that should strip a magic space -->
-    <string name="magic_space_stripping_symbols">\u0009\u0020\u0027\n-/_</string>
+    <string name="weak_space_stripping_symbols">"&#x0009;&#x0020;\'\n-/_"</string>
     <!-- Symbols that should promote magic spaces into real space -->
-    <string name="magic_space_promoting_symbols">;:!?([*&amp;@{&lt;&gt;+=|</string>
+    <string name="phantom_space_promoting_symbols">;:!?([*&amp;@{&lt;&gt;+=|</string>
     <!-- Symbols that do NOT separate words -->
-    <string name="symbols_excluded_from_word_separators">\u0027</string>
+    <string name="symbols_excluded_from_word_separators">\'</string>
 </resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 7b89edd..f2c7fbe 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -21,12 +21,14 @@
 <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">"Clavier Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Clavier Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Paramètres du clavier Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correcteur Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Correcteur orthographique Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Correcteur orthographique Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Paramètre du correcteur orthographique"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Utiliser données proximité"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Utiliser algorithme de proximité clavier pour correcteur ortho"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Rechercher noms contacts"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Correcteur orthographique utilise entrées de liste de contacts."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Son à chaque touche"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Agrandir les caractères"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Correction du texte"</string>
     <string name="misc_category" msgid="6894192814868233453">"Autres options"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Paramètres avancés"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Options destinées aux utilisateurs expérimentés"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Options destinées aux experts"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Autres modes de saisie"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"La touche de sélection de langue couvre d\'autres modes de saisie."</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Suppr. touche sélect. langue"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Masquer touche agrandie"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sans délai"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Par défaut"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Toujours afficher"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Afficher en mode Portrait"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Toujours masquer"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Afficher touche param."</string>
     <string name="auto_correction" msgid="4979925752001319458">"Correction automatique"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corriger autom. orthographe (pression sur barre espace/signes ponctuation)"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : enregistré"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Suiv."</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Préc."</string>
     <string name="label_done_key" msgid="2441578748772529288">"OK"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Envoi"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Aucun texte saisi"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Code touche %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Touche Maj activée"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Verrouillage des majuscules activé"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Touche Maj activée (appuyer pour désactiver)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Verrouillage des majuscules activé (appuyer pour désactiver)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Supprimer"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboles"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Lettres"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Saisie vocale"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Émoticône"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Entrée"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Virgule"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Point"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Parenthèse gauche"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Parenthèse droite"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Deux-points"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Point-virgule"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Point d\'exclamation"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Point d\'interrogation"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Guillemets doubles"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Apostrophe"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Point"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Racine carrée"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marque commerciale"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"à l\'attention de"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Étoile"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Dièse"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipse"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Guillemets bas doubles"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Saisie vocale"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La saisie vocale n\'est pas encore prise en charge pour votre langue, mais elle fonctionne en anglais."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La saisie vocale fait appel à la reconnaissance vocale de Google. Les "<a href="http://m.google.com/privacy">"Règles de confidentialité Google Mobile"</a>" s\'appliquent."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Pour désactiver la saisie vocale, accédez aux paramètres du mode de saisie."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Pour utiliser la saisie vocale, appuyez sur la touche du microphone."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Parlez maintenant"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Traitement en cours"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Erreur. Veuillez réessayer."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Connexion impossible"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Erreur, discours trop long."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problème audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Erreur serveur"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Aucune requête vocale détectée"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Aucune correspondance n\'a été trouvée."</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Recherche vocale non installée"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Astuce :"</b>" Faites glisser votre doigt sur le clavier pour parler."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Astuce :"</b>" La prochaine fois, essayez de prononcer la ponctuation, en énonçant des termes tels que \"point\", \"virgule\" ou \"point d\'interrogation\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Annuler"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Touche Maj activée"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Verrouillage des majuscules activé"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Touche Maj désactivée"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mode Symboles"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mode Lettres"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mode Téléphone"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mode Symboles du téléphone"</string>
     <string name="voice_input" msgid="3583258583521397548">"Touche de saisie vocale"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Sur clavier principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Sur clavier symboles"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micro sur le clavier principal"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micro sur clavier symboles"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Saisie vocale désactivée"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Sélectionner un mode de saisie."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurer les modes de saisie"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Langues de saisie"</string>
     <string name="select_language" msgid="3693815588777926848">"Langues de saisie"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Appuyer de nouveau pour enregistrer"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Appuyer de nouveau pour enregistrer"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionnaire disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Autoriser les commentaires des utilisateurs"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Contribuer à l\'amélioration de cet éditeur du mode de saisie grâce à l\'envoi automatique de statistiques d\'utilisation et de rapports d\'incident à Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Thème du clavier"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Clavier QWERTY allemand"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglais (Royaume-Uni)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode d\'étude de l\'utilisabilité"</string>
diff --git a/java/res/values-hi/donottranslate-more-keys.xml b/java/res/values-hi/donottranslate-more-keys.xml
new file mode 100644
index 0000000..19bcb9d
--- /dev/null
+++ b/java/res/values-hi/donottranslate-more-keys.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0967: "१" DEVANAGARI DIGIT ONE -->
+    <string name="keylabel_for_symbols_1">&#x0967;</string>
+    <!-- U+0968: "२" DEVANAGARI DIGIT TWO -->
+    <string name="keylabel_for_symbols_2">&#x0968;</string>
+    <!-- U+0969: "३" DEVANAGARI DIGIT THREE -->
+    <string name="keylabel_for_symbols_3">&#x0969;</string>
+    <!-- U+096A: "४" DEVANAGARI DIGIT FOUR -->
+    <string name="keylabel_for_symbols_4">&#x096A;</string>
+    <!-- U+096B: "५" DEVANAGARI DIGIT FIVE -->
+    <string name="keylabel_for_symbols_5">&#x096B;</string>
+    <!-- U+096C: "६" DEVANAGARI DIGIT SIX -->
+    <string name="keylabel_for_symbols_6">&#x096C;</string>
+    <!-- U+096D: "७" DEVANAGARI DIGIT SEVEN -->
+    <string name="keylabel_for_symbols_7">&#x096D;</string>
+    <!-- U+096E: "८" DEVANAGARI DIGIT EIGHT -->
+    <string name="keylabel_for_symbols_8">&#x096E;</string>
+    <!-- U+096F: "९" DEVANAGARI DIGIT NINE -->
+    <string name="keylabel_for_symbols_9">&#x096F;</string>
+    <!-- U+0966: "०" DEVANAGARI DIGIT ZERO -->
+    <string name="keylabel_for_symbols_0">&#x0966;</string>
+    <string name="additional_more_keys_for_symbols_1">1</string>
+    <string name="additional_more_keys_for_symbols_2">2</string>
+    <string name="additional_more_keys_for_symbols_3">3</string>
+    <string name="additional_more_keys_for_symbols_4">4</string>
+    <string name="additional_more_keys_for_symbols_5">5</string>
+    <string name="additional_more_keys_for_symbols_6">6</string>
+    <string name="additional_more_keys_for_symbols_7">7</string>
+    <string name="additional_more_keys_for_symbols_8">8</string>
+    <string name="additional_more_keys_for_symbols_9">9</string>
+    <string name="additional_more_keys_for_symbols_0">0</string>
+</resources>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index de95ab8..2e2b776 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android कीबोर्ड"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android कीबोर्ड (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android कीबोर्ड सेटिंग"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"इनपुट विकल्‍प"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android correction"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android वर्तनी परीक्षक"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android वर्तनी परीक्षक (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"वर्तनी जांच सेटिंग"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"निकटस्थ डेटा उपयोग करें"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"वर्तनी जांचने के लि‍ए कीबोर्ड जैसे नि‍कटस्‍थ एल्‍गोरि‍दम का उपयोग करें"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"संपर्क नामों को खोजें"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"वर्तनी परीक्षक आपकी संपर्क सूची की प्रविष्टियों का उपयोग करता है"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"कुंजी दबाने पर कंपन करता है"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"कुंजी दबाने पर आवाज"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"कुंजी दबाने पर पॉपअप दिखाएं"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"पाठ सुधार"</string>
     <string name="misc_category" msgid="6894192814868233453">"अन्य विकल्प"</string>
     <string name="advanced_settings" msgid="362895144495591463">"उन्नत सेटिंग"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"विशेषज्ञ उपयोगकर्ताओं के लिए विकल्‍प"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"विशेषज्ञों के लिए विकल्‍प"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"अन्‍य इनपुट पद्धतियों पर जाएं"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"भाषा स्‍विच कुंजी में अन्‍य इनपुट पद्धतियां भी शामिल हैं"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"भाषा स्‍विच कुंजी रोकें"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"कुंजी पॉपअप खारिज़ विलंब"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"कोई विलंब नहीं"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"डिफ़ॉल्ट"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"हमेशा दिखाएं"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"र्पोट्रेट मोड पर प्रदर्शित करें"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"हमेशा छुपाएं"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"सेटिंग कुंजी प्रदर्शित करता है"</string>
     <string name="auto_correction" msgid="4979925752001319458">"स्‍वत: सुधार"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacebar और विराम चिह्न गलत लिखे गए शब्‍दों को स्‍वचालित रूप से ठीक करते हैं"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"बंद"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: सहेजा गया"</string>
     <string name="label_go_key" msgid="1635148082137219148">"जाएं"</string>
     <string name="label_next_key" msgid="362972844525672568">"अगला"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"पिछला"</string>
     <string name="label_done_key" msgid="2441578748772529288">"पूर्ण"</string>
     <string name="label_send_key" msgid="2815056534433717444">"भेजें"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"कोई पाठ दर्ज नहीं किया गया"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"कुंजी कोड %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"शिफ़्ट"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"शिफ़्ट सक्षम किया गया है"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"कैप्स लॉक सक्षम किया गया है"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift चालू (अक्षम करने के लिए टैप करें)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock चालू (अक्षम करने के लिए टैप करें)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"डिलीट"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"प्रतीक"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"अक्षर"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"ध्‍वनि इनपुट"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"मुस्कुराता चेहरा"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"रिटर्न"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"अल्पविराम"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"पूर्णविराम"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"बायां कोष्ठक"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"दायां कोष्ठक"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"अपूर्ण विराम"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"अर्द्धविराम"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"विस्मयादिबोधक चिह्न"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"प्रश्नचिह्न"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"दोहरा उद्धरण"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"एकल उद्धरण"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"बिंदु"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"वर्गमूल"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"पाइ"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"डेल्टा"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"ट्रेडमार्क"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"संरक्षक"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"तारा"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"पाउंड"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"पदलोप चिह्न"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"निम्न दोहरा उद्धरण"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"ध्‍वनि इनपुट"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ध्‍वनि इनपुट आपकी भाषा के लिए अभी समर्थित नहीं है, पर अंग्रेज़ी में कार्य करता है."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ध्‍वनि इनपुट Google की वाक् पहचान का उपयोग करता है. "<a href="http://m.google.com/privacy">"मोबाइल गोपनीयता नीति"</a>" लागू होती है."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"ध्‍वनि इनपुट बंद करने के लिए, इनपुट पद्धति सेटिंग पर जाएं."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"ध्‍वनि इनपुट का उपयोग करने के लिए, माइक्रोफ़ोन बटन दबाएं."</string>
-    <string name="voice_listening" msgid="467518160751321844">"अब बोलें"</string>
-    <string name="voice_working" msgid="6666937792815731889">"कार्य कर रहा है"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"त्रुटि. कृपया पुन: प्रयास करें."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"कनेक्‍ट नहीं कर सका"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"त्रुटि, बहुत अधिक बातचीत."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"ऑडियो समस्या"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"सर्वर त्रुटि"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"कोई बातचीत नहीं सुनाई दी"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"कोई मिलान नहीं मिले"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"ध्‍वनि खोज इंस्टॉल नहीं है"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"संकेत:"</b>" बोलने के लिए कीबोर्ड पर स्‍वाइप करें"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"संकेत:"</b>" अगली बार, विरामचिन्‍ह बोलने का प्रयास करें जैसे \"पूर्णविराम\", \"अल्‍पविराम\", या \"प्रश्‍नचिह्न\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"रद्द करें"</string>
-    <string name="ok" msgid="7898366843681727667">"ठीक"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift सक्षम किया गया"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock सक्षम किया गया"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift अक्षम किया गया"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"प्रतीक मोड"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"अक्षर मोड"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"फ़ोन मोड"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"फ़ोन प्रतीक मोड"</string>
     <string name="voice_input" msgid="3583258583521397548">"ध्‍वनि‍ इनपुट कुंजी"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"मुख्‍य कीबोर्ड पर"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"प्रतीक कीबोर्ड पर"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"मुख्‍य कीबोर्ड पर माइक"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"प्रतीक कीबोर्ड पर माइक"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ध्‍वनि इनपुट अक्षम है"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"इनपुट विधि का चयन करें"</string>
     <string name="configure_input_method" msgid="373356270290742459">"इनपुट पद्धति कॉन्‍फ़िगर करें"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"इनपुट भाषा"</string>
     <string name="select_language" msgid="3693815588777926848">"इनपुट भाषाएं"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← सहेजने के लिए फिर से स्‍पर्श करें"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"शब्‍दकोश उपलब्‍ध है"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"उपयोगकर्ता फ़ीडबैक सक्षम करें"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"उपयोग के आंकड़े और क्रैश रिपोर्ट Google को स्वचालित रूप से भेज कर इस इनपुट पद्धति संपादक को बेहतर बनाने में सहायता करें."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"कीबोर्ड थीम"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"जर्मन QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"अंग्रेज़ी (यूके)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"उपयोगिता अध्ययन मोड"</string>
diff --git a/java/res/values-hr/donottranslate-more-keys.xml b/java/res/values-hr/donottranslate-more-keys.xml
index c34e0e6..9b4005d 100644
--- a/java/res/values-hr/donottranslate-more-keys.xml
+++ b/java/res/values-hr/donottranslate-more-keys.xml
@@ -18,10 +18,21 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_s">š,ś,ß</string>
-    <string name="more_keys_for_n">ñ,ń</string>
-    <string name="more_keys_for_y"></string>
-    <string name="more_keys_for_z">6,ž,ź,ż</string>
-    <string name="more_keys_for_c">č,ć,ç</string>
-    <string name="more_keys_for_d">đ</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S -->
+    <string name="more_keys_for_s">&#x0161;,&#x015B;,&#x00DF;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017A;,&#x017C;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA -->
+    <string name="more_keys_for_c">&#x010D;,&#x0107;,&#x00E7;</string>
+    <!-- U+0111: "đ" LATIN SMALL LETTER D WITH STROKE -->
+    <string name="more_keys_for_d">&#x0111;</string>
 </resources>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index cf63880..e416816 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android tipkovnica"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android tipkovnica (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Postavke tipkovnice za Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcije ulaza"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Ispravak za Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidova provjera pravopisa"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidova provjera pravopisa (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Postavke provjere pravopisa"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Upotreba podataka blizine"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Za prov. pravopisa upotrijebi algoritam blizine kao na tipkovnici"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Potražite imena kontakata"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Provjera pravopisa upotrebljava unose iz vašeg popisa kontakata"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibracija pri pritisku na tipku"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk pri pritisku tipke"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Povećanja na pritisak tipke"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Ispravak teksta"</string>
     <string name="misc_category" msgid="6894192814868233453">"Ostale opcije"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Napredne postavke"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opcije za stručne korisnike"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opcije za stručnjake"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Prebaci na druge unose"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Tipka za prebacivanje jezika pokriva i druge načine unosa"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Spriječi tipku za jezike"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Odgoda prikaza tipki"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez odgode"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Zadano"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Uvijek prikaži"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Prikaži u portretnom načinu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Uvijek sakrij"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Prikaži tipku postavki"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Samoispravak"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Razmak i interpunkcija automatski ispravljaju krive riječi"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Isključeno"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Spremljeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Idi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Dalje"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Pret."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Gotovo"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Pošalji"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nije unesen tekst"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kôd tipke %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift je omogućen"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock omogućen"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Uključena tipka Shift (dotaknite da onemogućite)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Uključeno je pisanje velikim slovima (Caps Lock) (dotaknite da onemogućite)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simboli"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Slova"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Glasovni unos"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smješko"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Zarez"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Točka"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Lijeva zagrada"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Desna zagrada"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dvotočka"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Točka-zarez"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Uskličnik"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Upitnik"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dvostruki navodnici"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Jednostruki navodnici"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Točka"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Kvadratni korijen"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Zaštitni znak"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"U ruke"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Zvjezdica"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"funta"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Tri točke"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Donji dvostruki navodnici"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Glasovni ulaz"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Vaš jezik trenutno nije podržan za glasovni unos, ali radi za engleski."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Glasovni unos upotrebljava Googleovo prepoznavanje govora. Primjenjuju se "<a href="http://m.google.com/privacy">"Pravila o privatnosti za uslugu Mobile"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Za isključivanje glasovnog unosa idite na postavke načina unosa."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Za upotrebu glasovnog unosa pritisnite gumb mikrofona."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Govorite sad"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Obrada"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Pogreška. Pokušajte ponovo."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Spajanje nije bilo moguće"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Pogreška, predugi govor."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problem sa zvukom"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Pogreška na poslužitelju"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nije se čuo govor"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nisu pronađeni rezultati"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Glasovno pretraživanje nije instalirano"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Savjet:"</b>" Prijeđite preko tipkovnice pa govorite"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Savjet:"</b>" Sljedeći put pokušajte izgovoriti znakove interpunkcije poput \"točka, \"zarez\" ili \"upitnik\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Odustani"</string>
-    <string name="ok" msgid="7898366843681727667">"U redu"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Omogućena tipka Shift"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Omogućeno pisanje velikih slova"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Onemogućena tipka Shift"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Način unosa simbola"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Način pisanja slova"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefonski način rada"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Način unosa telefonskih simbola"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tipka za glasovni unos"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Na glavnoj tipkovnici"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Na tipkovnici simb."</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon na gl. tipkovnici"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mik. na tipk. simb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Glas. unos onemog."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Odabir ulazne metode"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfiguriraj načine ulaza"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Jezici unosa"</string>
     <string name="select_language" msgid="3693815588777926848">"Jezici unosa"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Dodirnite opet za spremanje"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Rječnik je dostupan"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Omogući korisničke povratne informacije"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Pomozite u poboljšanju ovog urednika ulazne metode automatskim slanjem statistike upotrebe i padova Googleu."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema tipkovnice"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"njemački QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engleski (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engleski (SAD)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Način studije upotrebljivosti"</string>
diff --git a/java/res/values-hu/donottranslate-more-keys.xml b/java/res/values-hu/donottranslate-more-keys.xml
index 42b3301..4825910 100644
--- a/java/res/values-hu/donottranslate-more-keys.xml
+++ b/java/res/values-hu/donottranslate-more-keys.xml
@@ -18,11 +18,45 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">á,à,â,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,é,è,ê,ë,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,í,î,ï,ì,į,ī</string>
-    <string name="more_keys_for_o">9,ó,ö,ő,ô,ò,õ,œ,ø,ō</string>
-    <string name="more_keys_for_u">7,ú,ü,ű,û,ù,ū</string>
-    <string name="more_keys_for_y"></string>
-    <string name="more_keys_for_z">6</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E0;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EE;,&#x00EF;,&#x00EC;,&#x012F;,&#x012B;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F6;,&#x0151;,&#x00F4;,&#x00F2;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x0171;,&#x00FB;,&#x00F9;,&#x016B;</string>
 </resources>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 180b6fc..7e466ed 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android-billentyűzet"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-billentyűzet (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android billentyűzetbeállítások"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Beviteli beállítások"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android korrekció"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidos helyesírás-ellenőrző"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidos helyesírás-ellenőrző (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Helyesírás-ellenőrzés beállításai"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Közelségi adatok haszn."</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Billentyűzetszerű algoritmus a helyesírás-ellenőrzéshez"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Névjegyek keresése"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"A helyesírás-ellenőrző használja a névjegyek bejegyzéseit"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rezgés billentyű megnyomása esetén"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Hangjelzés billentyű megnyomása esetén"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Legyen nagyobb billentyű lenyomásakor"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Szövegjavítás"</string>
     <string name="misc_category" msgid="6894192814868233453">"Egyéb beállítások"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Speciális beállítások"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Beállítások gyakorlott felhasználóknak"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Beállítások gyakorlott felhasználóknak"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Váltás más beviteli módra"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"A nyelvkapcsoló gomb egyéb beviteli módokat is tartalmaz"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"A nyelvkapcsoló elrejtése"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Gombeltüntetés késése"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Nincs késés"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Alapbeállítás"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mindig látszik"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Megjelenítés álló tájolásban"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Mindig rejtve"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Beállítások billentyű megjelenítése"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automatikus javítás"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Szóköz és központozás automatikusan javítja az elgépelést"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Ki"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : mentve"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ugrás"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tovább"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Előző"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Kész"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Küldés"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Szöveg nincs megadva"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Billentyűkód: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift engedélyezve"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock bekapcsolva"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift be van kapcsolva (érintse meg a kikapcsoláshoz)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock be van kapcsolva (érintse meg a kikapcsoláshoz)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Törlés"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Szimbólumok"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Betűk"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Hangbevitel"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Mosolygós arc"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Vessző"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Pont"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Nyitó zárójel"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Berekesztő zárójel"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Kettőspont"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Pontosvessző"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Felkiáltójel"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Kérdőjel"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dupla idézőjel"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Szimpla idézőjel"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Pont"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Négyzetgyök"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Védjegy"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Százalék"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Csillag"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Kettős kereszt"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Kihagyás"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Alsó dupla idézőjel"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Hangbevitel"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"A hangbevitel szolgáltatás jelenleg nem támogatja az Ön nyelvét, ám angolul működik."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A hangbevitel a Google beszédfelismerő technológiáját használja, amelyre a "<a href="http://m.google.com/privacy">"Mobil adatvédelmi irányelvek"</a>" érvényesek."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"A hangbevitelt a beviteli mód beállításai között lehet kikapcsolni."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"A hangbevitel használatához nyomja meg a mikrofon gombot."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Most beszéljen"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Feldolgozás"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Hiba történt. Kérjük, próbálja újra."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Nem sikerült kapcsolódni"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Hiba történt; túl sokat beszélt."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Hangprobléma"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Szerverhiba"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nem hallatszott beszéd"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nem található egyezés"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"A hangalapú keresés nincs telepítve"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Tipp:"</b>" húzza végig az ujját a billentyűzeten a beszédhez"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Tipp:"</b>" következő alkalommal próbálja ki az írásjelek kimondását is, pl. \"period\", \"comma\" vagy \"question mark\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Mégse"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift bekapcsolva"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock bekapcsolva"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift kikapcsolva"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"\"Szimbólumok\" mód"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"\"Betű\" mód"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"\"Telefon\" mód"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"\"Telefonos szimbólumok\" mód"</string>
     <string name="voice_input" msgid="3583258583521397548">"Hangbeviteli gomb"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"A fő billentyűzeten"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Szimbólumoknál"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr. a billentyűzeten"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. a szimbólumoknál"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hangbevivel KI"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Beviteli mód kiválasztása"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Beviteli módok beállítása"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Beviteli nyelvek"</string>
     <string name="select_language" msgid="3693815588777926848">"Beviteli nyelvek"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Érintse meg újra a mentéshez"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Van elérhető szótár"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Felhasználói visszajelzés engedélyezése"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Segíthet ennek a beviteli módszernek a javításában, ha engedélyezi a használati statisztikák és a hibajelentések elküldését a Google-nak."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Billentyűzettéma"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Német QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"angol (brit)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Használhatósági teszt"</string>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index a023b64..39b41c0 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -21,12 +21,14 @@
 <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">"Keyboard Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Keyboard Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Setelan keyboard Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opsi masukan"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Koreksi android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Pemeriksa ejaan Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Pemeriksa ejaan Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Setelan pemeriksaan ejaan"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Gunakan data kedekatan"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Gunakan algoritme kedekatan seperti keyboard untuk memeriksa ejaan"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Pemeriksa ejaan menggunakan entri dari daftar kenalan Anda"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar jika tombol ditekan"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Berbunyi jika tombol ditekan"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Muncul saat tombol ditekan"</string>
@@ -34,10 +36,13 @@
     <string name="correction_category" msgid="2236750915056607613">"Koreksi teks"</string>
     <string name="misc_category" msgid="6894192814868233453">"Opsi lain"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Setelan lanjutan"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Pilihan untuk pengguna ahli"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opsi untuk ahli"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Beralih ke metode masukan lain"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Tombol beralih bahasa juga mencakup metode masukan lain"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Redam tombol alih bahasa"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Tundaan singkir munculan kunci"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Tanpa penundaan"</string>
-    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Bawaan"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sarankan nama Kenalan"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama dari Kenalan untuk saran dan koreksi"</string>
     <string name="enable_span_insert" msgid="7204653105667167620">"Aktifkan koreksi ulang"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Selalu tampilkan"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Tampilkan pada mode potret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Selalu sembunyikan"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Lihat tombol setelan"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Koreksi otomatis"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bilah spasi dan tanda baca secara otomatis dikoreksi pada kata yang salah ketik"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Mati"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Telah disimpan"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Buka"</string>
     <string name="label_next_key" msgid="362972844525672568">"Berikutnya"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Sblm"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Selesai"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Kirimkan"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Tidak ada teks yang dimasukkan"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kode tombol %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift diaktifkan"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock diaktifkan"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aktif (ketuk untuk menonaktifkan)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock aktif (ketuk untuk menonaktifkan)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Hapus"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simbol"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Huruf"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Masukan suara"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Wajah tersenyum"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Kembali"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Koma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Titik"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Kurung tutup"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Kurung buka"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Titik Dua"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Titik koma"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Tanda seru"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Tanda tanya"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Tanda petik"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Petik tunggal"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Titik"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Akar pangkat dua"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Merek dagang"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Dengan alamat"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Bintang"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Pon"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Elipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Tanda petik bawah"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Masukan suara"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Masukan suara saat ini tidak didukung untuk bahasa Anda, tetapi bekerja dalam Bahasa Inggris."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Masukan suara menggunakan pengenalan ucapan Google. "<a href="http://m.google.com/privacy">"Kebijakan Privasi Seluler"</a>" berlaku."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Untuk mematikan masukan suara, buka setelan metode masukan."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Untuk menggunakan masukan suara, tekan tombol mikrofon."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Ucapkan sekarang"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Bekerja"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Galat: Coba lagi."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Tidak dapat menyambung"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Galat, terlalu banyak ucapan."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Masalah audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Galat server"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Tidak terdengar ucapan"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Tak ditemukan yang cocok"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Penelusuran suara tidak terpasang"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Petunjuk:"</b>" Gesek keyboard untuk berbicara"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Petunjuk:"</b>" Selanjutnya, coba ucapkan tanda baca seperti \"titik\", \"koma\", atau \"tanda tanya\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Batal"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift diaktifkan"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock diaktifkan"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift dinonaktifkan"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mode simbol"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mode huruf"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mode telepon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mode simbol telepon"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tombol masukan suara"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Pada keyboard utama"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Pada keyboard simbol"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mik pada keyboard utama"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mik pada keyboard simbol"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Masukan suara dinonaktifkan"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Pilih metode masukan"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan metode masukan"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Bahasa masukan"</string>
     <string name="select_language" msgid="3693815588777926848">"Bahasa masukan"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Sentuh sekali lagi untuk menyimpan"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Kamus yang tersedia"</string>
-    <string name="prefs_enable_log" msgid="6620424505072963557">"Aktifkan umpan balik pengguna"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Aktifkan masukan pengguna"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Bantu tingkatkan metode editor masukan dengan mengirim statistik penggunaan dan laporan kerusakan ke Google secara otomatis."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema keyboard"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY Jerman"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inggris (Inggris)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inggris (AS)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modus studi daya guna"</string>
diff --git a/java/res/values-is/donottranslate-more-keys.xml b/java/res/values-is/donottranslate-more-keys.xml
new file mode 100644
index 0000000..284aae9
--- /dev/null
+++ b/java/res/values-is/donottranslate-more-keys.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E4;,&#x00E6;,&#x00E5;,&#x00E0;,&#x00E2;,&#x00E3;,&#x0101;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00EB;,&#x00E8;,&#x00EA;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EF;,&#x00EE;,&#x00EC;,&#x012F;,&#x012B;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F6;,&#x00F4;,&#x00F2;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x00FB;,&#x00F9;,&#x016B;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+00F0: "ð" LATIN SMALL LETTER ETH -->
+    <string name="more_keys_for_d">&#x00F0;</string>
+    <!-- U+00FE: "þ" LATIN SMALL LETTER THORN -->
+    <string name="more_keys_for_t">&#x00FE;</string>
+    <!-- U+00F0: "ð" LATIN SMALL LETTER ETH -->
+    <string name="keylabel_for_nordic_row1_11">&#x00F0;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="keylabel_for_nordic_row2_10">&#x00E6;</string>
+    <!-- U+00FE: "þ" LATIN SMALL LETTER THORN -->
+    <string name="keylabel_for_nordic_row2_11">&#x00FE;</string>
+</resources>
diff --git a/java/res/values-it/donottranslate-more-keys.xml b/java/res/values-it/donottranslate-more-keys.xml
index fa1537b..17dd031 100644
--- a/java/res/values-it/donottranslate-more-keys.xml
+++ b/java/res/values-it/donottranslate-more-keys.xml
@@ -18,9 +18,45 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,á,â,ä,æ,ã,å,ā,ª</string>
-    <string name="more_keys_for_e">3,è,é,ê,ë,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,ì,í,î,ï,į,ī</string>
-    <string name="more_keys_for_o">9,ò,ó,ô,ö,õ,œ,ø,ō,º</string>
-    <string name="more_keys_for_u">7,ù,ú,û,ü,ū</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E1;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;,&#x00AA;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00EC;,&#x00ED;,&#x00EE;,&#x00EF;,&#x012F;,&#x012B;</string>
+    <!-- U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+    <string name="more_keys_for_o">&#x00F2;,&#x00F3;,&#x00F4;,&#x00F6;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;,&#x00BA;</string>
+    <!-- U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00F9;,&#x00FA;,&#x00FB;,&#x00FC;,&#x016B;</string>
 </resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 58c0e2c..3e27f4e 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -21,12 +21,14 @@
 <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">"Tastiera Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tastiera Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Impostazioni tastiera Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opzioni inserimento"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correzione Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Controllo ortografico Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Controllo ortografico Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Impostazioni di controllo ortografico"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Usa i dati di prossimità"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Usa algoritmo prossimità (come in tastiere) per controllo ortografico"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca in nomi contatti"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"La funzione di controllo ortografico usa voci dell\'elenco contatti"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrazione tasti"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Suono tasti"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup sui tasti"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Correzione testo"</string>
     <string name="misc_category" msgid="6894192814868233453">"Altre opzioni"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Impostazioni avanzate"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opzioni per utenti esperti"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opzioni per esperti"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Altri metodi immissione"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Il tasto per cambiare lingua offre altri metodi di immissione"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Elimina tasto cambio lingua"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Ritardo eliminaz. popup tasto"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Nessun ritardo"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predefinito"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostra sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostra in modalità verticale"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Nascondi sempre"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Mostra tasto impostazioni"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Correzione automatica"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Barra spaziatrice/punteggiatura correggono parole con errori"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : parola salvata"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Vai"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avanti"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Indietro"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Fine"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Invia"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nessun testo inserito"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Codice tasto %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Maiuscolo"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Maiuscolo attivo"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Blocco maiuscole attivo"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Maiuscolo attivo (tocca per disattivare)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Blocco maiuscole attivo (tocca per disattivare)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Cancella"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simboli"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Lettere"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Input vocale"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smile"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Invio"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Virgola"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punto"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Parentesi aperta"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Parentesi chiusa"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Due punti"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Punto e virgola"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Punto esclamativo"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Punto interrogativo"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Virgolette doppie"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Virgolette semplici"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Pallino"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Radice quadrata"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi greco"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marchio commerciale"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Presso"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Asterisco"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Cancelletto"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellissi"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Virgolette doppie basse"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Comandi vocali"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"I comandi vocali non sono attualmente supportati per la tua lingua ma funzionano in inglese."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"L\'input vocale utilizza il riconoscimento vocale di Google. Sono valide le "<a href="http://m.google.com/privacy">"norme sulla privacy di Google Mobile"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Per disattivare l\'input vocale, vai alle impostazioni del metodo di input."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Per utilizzare l\'input vocale, premi il pulsante del microfono."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Parla ora"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Elaborazione..."</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Errore. Riprova più tardi."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Impossibile connettersi."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Errore: conversazione troppo lunga."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Errore del server"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nessuna frase vocale rilevata"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nessuna corrispondenza trovata"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Ricerca vocale non installata"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Suggerimento."</b>" Fai scorrere il dito sulla tastiera per parlare"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Suggerimento."</b>" La prossima volta, prova a pronunciare termini relativi alla punteggiatura come \"punto\", \"virgola\" o \"punto di domanda\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Annulla"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Maiuscolo attivo"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Blocco maiuscole attivo"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Maiuscolo disattivato"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Modalità simboli"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Modalità lettere"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Modalità telefono"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Modalità simboli telefono"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tasto immissione vocale"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Su tastiera principale"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Su tastiera simboli"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic su tastiera principale"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic su tastiera simboli"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Comandi vocali disatt."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Seleziona metodo di inserimento"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configura metodi di immissione"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Lingue comandi"</string>
     <string name="select_language" msgid="3693815588777926848">"Lingue comandi"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tocca di nuovo per salvare"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tocca di nuovo per salvare"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dizionario disponibile"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Attiva commenti degli utenti"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Aiuta a migliorare l\'editor del metodo di inserimento inviando automaticamente a Google statistiche sull\'utilizzo e segnalazioni sugli arresti anomali."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema della tastiera"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY tedesca"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglese (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglese (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modalità Studio sull\'usabilità"</string>
diff --git a/java/res/values-iw/donottranslate-more-keys.xml b/java/res/values-iw/donottranslate-more-keys.xml
index 829486f..c5431b6 100644
--- a/java/res/values-iw/donottranslate-more-keys.xml
+++ b/java/res/values-iw/donottranslate-more-keys.xml
@@ -18,6 +18,52 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_star">★</string>
-    <string name="more_keys_for_plus">±,﬩</string>
+    <!-- U+2605: "★" BLACK STAR -->
+    <string name="more_keys_for_star">&#x2605;</string>
+    <!-- U+00B1: "±" PLUS-MINUS SIGN
+         U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN -->
+    <string name="more_keys_for_plus">&#x00B1;,&#xFB29;</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- U+0029: ")" RIGHT PARENTHESIS -->
+    <integer name="keycode_for_left_parenthesis">0x0029</integer>
+    <!-- U+0028: "(" LEFT PARENTHESIS -->
+    <integer name="keycode_for_right_parenthesis">0x0028</integer>
+    <string name="more_keys_for_left_parenthesis">!fixedColumnOrder!3,&lt;|&gt;,{|},[|]</string>
+    <string name="more_keys_for_right_parenthesis">!fixedColumnOrder!3,&gt;|&lt;,}|{,]|[</string>
+    <!-- U+003E: ">" GREATER-THAN SIGN -->
+    <integer name="keycode_for_less_than">0x003E</integer>
+    <!-- U+003C: "<" LESS-THAN SIGN -->
+    <integer name="keycode_for_greater_than">0x003C</integer>
+    <!-- U+2264: "≤" LESS-THAN OR EQUAL TO
+         U+2265: "≥" GREATER-THAN EQUAL TO
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+         The following characters don't need BIDI mirroring.
+         U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
+    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
+    <!-- U+005D: "]" RIGHT SQUARE BRACKET -->
+    <integer name="keycode_for_left_square_bracket">0x005D</integer>
+    <!-- U+005B: "[" LEFT SQUARE BRACKET -->
+    <integer name="keycode_for_right_square_bracket">0x005B</integer>
+    <!-- U+007D: "}" RIGHT CURLY BRACKET -->
+    <integer name="keycode_for_left_curly_bracket">0x007D</integer>
+    <!-- U+007B: "{" LEFT CURLY BRACKET -->
+    <integer name="keycode_for_right_curly_bracket">0x007B</integer>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
 </resources>
diff --git a/java/res/values-fr-rCH/donottranslate-more-keys.xml b/java/res/values-iw/donottranslate.xml
similarity index 68%
copy from java/res/values-fr-rCH/donottranslate-more-keys.xml
copy to java/res/values-iw/donottranslate.xml
index 561c5e5..57de253 100644
--- a/java/res/values-fr-rCH/donottranslate-more-keys.xml
+++ b/java/res/values-iw/donottranslate.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,9 +18,8 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_y">ÿ</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z">6</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- Symbols that are suggested between words -->
+    <string name="suggested_punctuations">!,?,\\,,:,;,\",(|),)|(,\',-,/,@,_</string>
 </resources>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 292517c..2d8f8dc 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -21,12 +21,14 @@
 <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">"מקלדת Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"מקלדת Android ‏(AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"הגדרות מקלדת של Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"אפשרויות קלט"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"תיקון Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"בודק האיות של Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"בודק האיות של Android ‏(AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"הגדרות בדיקת איות"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"שימוש בנתוני הקירבה"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"השתמש באלגוריתם קירבה דמוי-מקלדת עבור בדיקת איות"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"חפש שמות של אנשי קשר"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"בודק האיות משתמש בערכים מרשימת אנשי הקשר שלך"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"רטט בלחיצה על מקשים"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"צלילים בעת לחיצה על מקשים"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"חלון קופץ בלחיצה על מקש"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"תיקון טקסט"</string>
     <string name="misc_category" msgid="6894192814868233453">"אפשרויות אחרות"</string>
     <string name="advanced_settings" msgid="362895144495591463">"הגדרות מתקדמות"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"אפשרויות עבור משתמשים מתקדמים"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"אפשרויות למומחים"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"עבור לשיטות קלט אחרות"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"מתג החלפת השפה מכסה גם שיטות קלט אחרות"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"הסתר את מתג החלפת השפה"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"עיכוב סגירת חלון קופץ של מקש"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ללא עיכוב"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ברירת מחדל"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"הצג תמיד"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"הצג בפריסה לאורך"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"הסתר תמיד"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"הצג מקש הגדרות"</string>
     <string name="auto_correction" msgid="4979925752001319458">"תיקון אוטומטי"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"מקש הרווח ופיסוק מתקנים אוטומטית שגיאות הקלדה"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"כבוי"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : נשמרה"</string>
     <string name="label_go_key" msgid="1635148082137219148">"בצע"</string>
     <string name="label_next_key" msgid="362972844525672568">"הבא"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"הקודם"</string>
     <string name="label_done_key" msgid="2441578748772529288">"סיום"</string>
     <string name="label_send_key" msgid="2815056534433717444">"שלח"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"אבג"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"לא הוזן טקסט"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"קוד מקש %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift מופעל"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock מופעל"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift פועל (הקש כדי להשבית)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock פועל (הקש כדי להשבית)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"מחק"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"סמלים"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"אותיות"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"קלט קולי"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"פרצוף סמיילי"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"חזור"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"פסיק"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"נקודה"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"סוגריים שמאליים"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"סוגריים ימניים"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"נקודתיים"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"נקודה פסיק"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"סימן קריאה"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"סימן שאלה"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"מרכאות כפולות"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"גרש בודד"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"נקודה"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"שורש ריבועי"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"פאי"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"דלתה"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"סימן מסחרי"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"לכבוד"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"כוכב"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"סולמית"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"שלוש נקודות"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"מרכאות כפולות תחתונות"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"קלט קולי"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"קלט קולי אינו נתמך בשלב זה בשפתך, אך הוא פועל באנגלית."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"קלט קולי משתמש בזיהוי דיבור של Google.‏ "<a href="http://m.google.com/privacy">"מדיניות הפרטיות של \'Google לנייד\'"</a>" חלה במקרה זה."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"כדי לכבות את הקלט הקולי, עבור להגדרות של שיטת קלט."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"כדי להשתמש בקלט קולי, לחץ על לחצן המיקרופון."</string>
-    <string name="voice_listening" msgid="467518160751321844">"דבר עכשיו"</string>
-    <string name="voice_working" msgid="6666937792815731889">"פועל"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"שגיאה. נסה שוב."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"אין אפשרות להתחבר"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"שגיאה, קטע דיבור ארוך מדי."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"בעיה באודיו"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"שגיאת שרת"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"לא ניתן לשמוע דיבור"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"לא נמצאו התאמות"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"חיפוש קולי לא מותקן"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"רמז:"</b>" העבר על המקלדת כדי לדבר"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"רמז:"</b>" בפעם הבאה, נסה לומר את סימני הפיסוק כגון \"נקודה\", \"פסיק\" או \"סימן שאלה\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"ביטול"</string>
-    <string name="ok" msgid="7898366843681727667">"אישור"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift מופעל"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock מופעל"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift מושבת"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"מצב סמלים"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"מצב אותיות"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"מצב טלפון"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"מצב סמלי טלפון"</string>
     <string name="voice_input" msgid="3583258583521397548">"מקש קלט קולי"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"במקלדת הראשית"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"במקלדת סמלים"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"מיקרופון במקלדת הראשית"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"מיקרופון במקלדת סמלים"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"הקלט הקולי מושבת"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"בחר שיטת קלט"</string>
     <string name="configure_input_method" msgid="373356270290742459">"הגדרת שיטות קלט"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"שפות קלט"</string>
     <string name="select_language" msgid="3693815588777926848">"שפות קלט"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← גע פעם נוספת לשמירה"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"מילון זמין"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"הפוך משוב ממשתמשים לפעיל"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"עזור לשפר את עורך שיטת הקלט על ידי שליחה אוטומטית של סטטיסטיקת שימוש ודוחות קריסת מחשב ל-Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"עיצוב מקלדת"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"מקלדת QWERTY גרמנית"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"אנגלית (בריטניה)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"אנגלית (ארה\"ב)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"מצב מחקר שימושיות"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 540bb46..439a652 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -21,12 +21,14 @@
 <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">"Androidキーボード"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Androidキーボード(AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Androidキーボードの設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Androidスペルチェッカー"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidスペルチェッカー"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidスペルチェッカー(AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"スペルチェックの設定"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"近接データを使用"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"スペルチェックでキーボードと同じような近接アルゴリズムを使用する"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"連絡先名の検索"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"スペルチェッカーでは連絡先リストのエントリを使用します"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"キー操作バイブ"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"キー操作音"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"キー押下時ポップアップ"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"テキストの修正"</string>
     <string name="misc_category" msgid="6894192814868233453">"他のオプション"</string>
     <string name="advanced_settings" msgid="362895144495591463">"詳細設定"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"上級ユーザー向けオプション"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"上級者向けオプション"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"他の入力方法に切り替え"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"言語切り替えキーは他の入力方法にも対応しています"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"言語切り替えキーを非表示"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"キーのポップアップ時間"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"すぐに消去"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"デフォルト"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"常に表示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"縦向きで表示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"常に非表示"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"設定キーを表示"</string>
     <string name="auto_correction" msgid="4979925752001319458">"自動修正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"誤入力をスペースまたは句読点キーで修正する"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"OFF"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:保存しました"</string>
     <string name="label_go_key" msgid="1635148082137219148">"実行"</string>
     <string name="label_next_key" msgid="362972844525672568">"次へ"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"前"</string>
     <string name="label_done_key" msgid="2441578748772529288">"完了"</string>
     <string name="label_send_key" msgid="2815056534433717444">"送信"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"テキストが入力されていません"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"キーコード:%d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift有効"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock有効"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift有効(タップして解除)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock有効(タップして解除)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"DEL"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"記号"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"英字"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"音声入力"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"顔文字"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"カンマ"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"ピリオド"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"左かっこ"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"右かっこ"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"コロン"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"セミコロン"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"感嘆符"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"疑問符"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"二重引用符"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"単一引用符"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"中点"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"平方根"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"円周率記号"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"デルタ"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"商標記号"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"宛名記号"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"アスタリスク"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"ナンバー記号"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"省略記号"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"下付き二重引用符"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"音声入力"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"音声入力は現在英語には対応していますが、日本語には対応していません。"</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"音声入力ではGoogleの音声認識技術を利用します。"<a href="http://m.google.com/privacy">"モバイルプライバシーポリシー"</a>"が適用されます。"</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"音声入力をOFFにするには、入力方法の設定を開きます。"</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"音声入力するには、マイクボタンを押してください。"</string>
-    <string name="voice_listening" msgid="467518160751321844">"お話しください"</string>
-    <string name="voice_working" msgid="6666937792815731889">"処理中"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"エラーです。もう一度お試しください。"</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"接続できませんでした"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"音声が長すぎてエラーになりました。"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"オーディオエラー"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"サーバーエラー"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"音声が聞き取れません"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"該当なし"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Voice Searchはインストールされていません"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"ヒント:"</b>" 音声入力するにはキーボードをスワイプします"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"ヒント:"</b>" 次回は句読点として「period」、「comma」、「question mark」などの音声入力を試してみてください。"</string>
-    <string name="cancel" msgid="6830980399865683324">"キャンセル"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift有効"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock有効"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift解除"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"記号モード"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"英数モード"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"電話モード"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"電話記号モード"</string>
     <string name="voice_input" msgid="3583258583521397548">"音声入力キー"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"メインキーボード上"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"記号キーボード上"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"メインキーボードのマイク"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"記号キーボードのマイク"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"音声入力は無効です"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"入力方法の選択"</string>
     <string name="configure_input_method" msgid="373356270290742459">"入力方法を設定"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"入力言語"</string>
     <string name="select_language" msgid="3693815588777926848">"入力言語"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"←保存するにはもう一度タップ"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"辞書を利用できます"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"ユーザーフィードバックを有効にする"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"IMEの機能向上のため、使用統計状況やクラッシュレポートをGoogleに自動送信します。"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"キーボードのテーマ"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"ドイツ語QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"英語(英国)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"英語(米国)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"使いやすさの研究モード"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index bc2b628..a2863c7 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android 키보드"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 키보드(AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 키보드 설정"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"입력 옵션"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 교정"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 맞춤법 검사기"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 맞춤법 검사기(AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"맞춤법 검사 설정"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"근접 데이터 사용"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"맞춤법 검사에 대해 키보드와 유사한 근접 알고리즘 사용"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"연락처 이름 조회"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"맞춤법 검사기가 주소록의 항목을 사용합니다."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"키를 누를 때 진동 발생"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"키를 누를 때 소리 발생"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"키를 누를 때 팝업"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"텍스트 수정"</string>
     <string name="misc_category" msgid="6894192814868233453">"기타 옵션"</string>
     <string name="advanced_settings" msgid="362895144495591463">"고급 설정"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"전문 사용자용 옵션"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"전문가용 옵션"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"다른 입력 방법으로 전환"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"언어 전환 키가 제공하는 기타 입력 방법"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"언어 전환 키 제거"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"키 팝업 해제 지연"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"지연 없음"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"기본값"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"항상 표시"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"세로 화면일 때만 표시"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"항상 숨기기"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"설정 키 표시"</string>
     <string name="auto_correction" msgid="4979925752001319458">"자동 수정"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"스페이스바와 문장부호 키를 사용하면 오타가 자동으로 교정됩니다."</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"사용 안함"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: 저장됨"</string>
     <string name="label_go_key" msgid="1635148082137219148">"이동"</string>
     <string name="label_next_key" msgid="362972844525672568">"다음"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"이전"</string>
     <string name="label_done_key" msgid="2441578748772529288">"완료"</string>
     <string name="label_send_key" msgid="2815056534433717444">"전송"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"입력한 텍스트 없음"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"키 코드 %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"시프트 키"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift 키 누름"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock 키 켜짐"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 사용(사용하지 않으려면 탭하세요.)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock 사용(사용하지 않으려면 탭하세요.)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"삭제 키"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"기호"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"문자"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"음성 입력"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"웃는 얼굴"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"리턴 키"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"쉼표"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"마침표"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"왼쪽 괄호"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"오른쪽 괄호"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"콜론"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"세미콜론"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"느낌표"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"물음표"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"큰따옴표"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"작은따옴표"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"점"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"제곱근"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"파이"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"델타"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"상표(™)"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"퍼센트 키"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"별표"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"파운드"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"생략 부호"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"아래쪽 큰따옴표"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"음성 입력"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"음성 입력은 현재 자국어로 지원되지 않으며 영어로 작동됩니다."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"음성 입력에서는 Google의 음성 인식 기능을 사용합니다. "<a href="http://m.google.com/privacy">"모바일 개인정보취급방침"</a>"이 적용됩니다."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"음성 입력을 사용하지 않으려면 입력 방법 설정으로 이동하세요."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"음성 입력을 사용하려면 마이크 버튼을 누르세요."</string>
-    <string name="voice_listening" msgid="467518160751321844">"지금 말하세요."</string>
-    <string name="voice_working" msgid="6666937792815731889">"인식 중"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"오류가 발생했습니다. 다시 시도해 보세요."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"연결할 수 없습니다."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"음성을 너무 많이 입력했습니다."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"오디오 문제"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"서버 오류"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"음성이 인식되지 않았습니다."</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"일치하는 항목 없음"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"음성 검색이 설치되지 않았습니다."</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"도움말:"</b>" 키보드 위로 손가락을 미끄러지듯 움직이고 나서 말하세요."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"도움말:"</b>" 다음 번에는 \'마침표\', \'쉼표\', \'물음표\'와 같은 구두점을 말해 보세요."</string>
-    <string name="cancel" msgid="6830980399865683324">"취소"</string>
-    <string name="ok" msgid="7898366843681727667">"확인"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift 사용"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock 사용"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift 사용중지"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"기호 모드"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"문자 모드"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"다이얼 모드"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"전화 기호 모드"</string>
     <string name="voice_input" msgid="3583258583521397548">"음성 입력 키"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"기본 키보드"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"기호 키보드"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"기본 키보드의 마이크"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"기호 키보드의 마이크"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"음성 입력이 사용 중지됨"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"입력 방법 선택"</string>
     <string name="configure_input_method" msgid="373356270290742459">"입력 방법 설정"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"입력 언어"</string>
     <string name="select_language" msgid="3693815588777926848">"입력 언어"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← 저장하려면 다시 터치하세요."</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"사전 사용 가능"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"사용자 의견 사용"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"사용 통계 및 충돌 보고서를 Google에 자동으로 전송하여 입력 방법 편집기의 개선에 도움을 줍니다."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"키보드 테마"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"독일어 QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"영어(영국)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"영어(미국)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"가용성 연구 모드"</string>
diff --git a/java/res/values-ky/donottranslate-more-keys.xml b/java/res/values-ky/donottranslate-more-keys.xml
new file mode 100644
index 0000000..fd90248
--- /dev/null
+++ b/java/res/values-ky/donottranslate-more-keys.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
+    <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
+    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <string name="keylabel_for_east_slavic_row3_5">&#x0438;</string>
+    <!-- U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U -->
+    <string name="more_keys_for_cyrillic_u">&#x04AF;</string>
+    <!-- U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER -->
+    <string name="more_keys_for_cyrillic_en">&#x04A3;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_ha">&#x044A;</string>
+    <!-- U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O -->
+    <string name="more_keys_for_cyrillic_o">&#x04E9;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
+</resources>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 593cb0a..1157b27 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -19,19 +19,18 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=0.260in -->
-    <dimen name="keyboardHeight">1.100in</dimen>
+    <!-- Preferable keyboard height in absolute scale: 1.100in -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">176.0dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
     <!-- key_height + key_bottom_gap = popup_key_height -->
-<!--    <dimen name="key_height">0.260in</dimen>-->
-    <dimen name="popup_key_height">0.280in</dimen>
+    <dimen name="popup_key_height">44.8dp</dimen>
 
     <fraction name="keyboard_top_padding">1.818%p</fraction>
     <fraction name="keyboard_bottom_padding">0.0%p</fraction>
     <fraction name="key_bottom_gap">4.330%p</fraction>
     <fraction name="key_horizontal_gap">0.405%p</fraction>
 
-    <dimen name="keyboardHeight_stone">0.984in</dimen>
     <fraction name="key_bottom_gap_stone">5.010%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.159%p</fraction>
 
@@ -44,7 +43,7 @@
     <fraction name="key_horizontal_gap_ics">1.020%p</fraction>
 
     <!-- left or right padding of label alignment -->
-    <dimen name="key_label_horizontal_padding">8dip</dimen>
+    <dimen name="key_label_horizontal_padding">8dp</dimen>
 
     <fraction name="key_letter_ratio">65%</fraction>
     <fraction name="key_large_letter_ratio">74%</fraction>
@@ -53,17 +52,20 @@
     <fraction name="key_hint_label_ratio">52%</fraction>
     <fraction name="key_uppercase_letter_ratio">40%</fraction>
     <fraction name="key_preview_text_ratio">90%</fraction>
-    <dimen name="key_preview_offset">0.08in</dimen>
+    <fraction name="spacebar_text_ratio">40.000%</fraction>
+    <dimen name="key_preview_offset">12.8dp</dimen>
 
-    <dimen name="key_preview_offset_ics">0.01in</dimen>
+    <dimen name="key_preview_offset_ics">1.6dp</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-22.4dp</dimen>
 
-    <dimen name="suggestions_strip_height">36dip</dimen>
-    <dimen name="more_suggestions_row_height">36dip</dimen>
+    <dimen name="suggestions_strip_height">36dp</dimen>
+    <dimen name="more_suggestions_row_height">36dp</dimen>
     <integer name="max_more_suggestions_row">2</integer>
     <fraction name="min_more_suggestions_width">60%</fraction>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">0.336in</dimen>
+    <dimen name="more_keys_keyboard_slide_allowance">53.76dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-0.280in</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-44.8dp</dimen>
 </resources>
diff --git a/java/res/values-land/keyboard-heights.xml b/java/res/values-land/keyboard-heights.xml
new file mode 100644
index 0000000..12c3e39
--- /dev/null
+++ b/java/res/values-land/keyboard-heights.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 1.100in -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Droid -->
+        <item>sholes,194.3333</item>
+        <!-- Nexus One -->
+        <item>mahimahi,186.2667</item>
+        <!-- Nexus S -->
+        <item>herring,171.9385</item>
+        <!-- Galaxy Nexus -->
+        <item>tuna,173.4207</item>
+    </string-array>
+</resources>
diff --git a/java/res/values-lt/donottranslate-more-keys.xml b/java/res/values-lt/donottranslate-more-keys.xml
index 6b81e45..1491d95 100644
--- a/java/res/values-lt/donottranslate-more-keys.xml
+++ b/java/res/values-lt/donottranslate-more-keys.xml
@@ -18,11 +18,90 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ą,à,á,â,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,ė,ę,è,é,ê,ë,ē</string>
-    <string name="more_keys_for_i">8,į,î,ï,ì,í,ī</string>
-    <string name="more_keys_for_u">7,ų,ū,û,ü,ù,ú</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
-    <string name="more_keys_for_c">č,ç,ć</string>
-    <string name="more_keys_for_z">ž,ź,ż</string>
+    <!-- U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="more_keys_for_a">&#x0105;,&#x00E4;,&#x0101;,&#x00E0;,&#x00E1;,&#x00E2;,&#x00E3;,&#x00E5;,&#x00E6;</string>
+    <!-- U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+011B: "ě" LATIN SMALL LETTER E WITH CARON -->
+    <string name="more_keys_for_e">&#x0117;,&#x0119;,&#x0113;,&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x011B;</string>
+    <!-- U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+0131: "ı" LATIN SMALL LETTER DOTLESS I -->
+    <string name="more_keys_for_i">&#x012F;,&#x012B;,&#x00EC;,&#x00ED;,&#x00EE;,&#x00EF;,&#x0131;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_o">&#x00F6;,&#x00F5;,&#x00F2;,&#x00F3;,&#x00F4;,&#x0153;,&#x0151;,&#x00F8;</string>
+    <!-- U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+         U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+         U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE -->
+    <string name="more_keys_for_u">&#x016B;,&#x0173;,&#x00FC;,&#x016B;,&#x00F9;,&#x00FA;,&#x00FB;,&#x016F;,&#x0171;</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA -->
+    <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;,&#x015F;</string>
+    <!-- U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+         U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x0146;,&#x00F1;,&#x0144;,&#x0144;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x010D;,&#x00E7;,&#x0107;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+010F: "ď" LATIN SMALL LETTER D WITH CARON -->
+    <string name="more_keys_for_d">&#x010F;</string>
+    <!-- U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+         U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+         U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE -->
+    <string name="more_keys_for_r">&#x0157;,&#x0159;,&#x0155;</string>
+    <!-- U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+         U+0165: "ť" LATIN SMALL LETTER T WITH CARON -->
+    <string name="more_keys_for_t">&#x0163;,&#x0165;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017C;,&#x017A;</string>
+    <!-- U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA -->
+    <string name="more_keys_for_k">&#x0137;</string>
+    <!-- U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+         U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+         U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+         U+013E: "ľ" LATIN SMALL LETTER L WITH CARON -->
+    <string name="more_keys_for_l">&#x013C;,&#x0142;,&#x013A;,&#x013E;</string>
+    <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+         U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
 </resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 6263f67..8349dcd 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -21,12 +21,14 @@
 <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">"„Android“ klaviatūra"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"„Android“ klaviatūra (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"„Android“ klaviatūros nustatymai"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Įvesties parinktys"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"„Android“ korekcijos"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"„Android“ rašybos tikrinimo programa"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"„Android“ rašybos tikrinimo programa (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Rašybos tikrinimo nustatymai"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Naudoti artimumo duomenis"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Naudokite klaviatūros tipo artimumo algoritmą rašybai patikrinti"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktų vardų paieška"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Rašybos tikrinimo progr. naudoja įrašus, esančius kontaktų sąraše"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibruoti, kai paspaudžiami klavišai"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Klavišo paspaudimo garsas"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Iššoka paspaudus klavišą"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Teksto taisymas"</string>
     <string name="misc_category" msgid="6894192814868233453">"Kitos parinktys"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Išplėstiniai nustatymai"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Parinktys ekspertams"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Parinktys ekspertams"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Perj. į kt. įvesties būd."</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Kalbos perjungimo klavišu taip pat perjungiami įvesties būdai"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Nerodyti klb. keit. klav."</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Pagr. išš. l. atsis. d."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Be delsos"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Numatytasis"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Visada rodyti"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Rodyti stačiuoju režimu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Visada slėpti"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Rodyti nustatymų raktą"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automatinis taisymas"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Tarpo kl. ir skyr. ženkl. aut. išt. neteis. įv. žodž."</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Išjungta"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: išsaugota"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pradėti"</string>
     <string name="label_next_key" msgid="362972844525672568">"Kitas"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Anks."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Atlikta"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Siųsti"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nėra įvesto teksto"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Klavišo kodas %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Antrojo lygio klavišas"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Antrojo lygio klavišas įgalintas"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Įgalintas didžiųjų raidžių klavišas"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Įjungtas antrasis lygis (palieskite, kad išjungtumėte)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Įjungtos didžiosios raidės (palieskite, kad išjungtumėte)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Ištrinti"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simboliai"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Raidės"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Įvestis balsu"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Šypsenėlė"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Grįžti"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Kablelis"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Taškas"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Kairysis skliaustas"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Dešinysis skliaustas"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dvitaškis"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Kabliataškis"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Šauktukas"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Klaustukas"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dvigubos kabutės"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Viengubos kabutės"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Taškas"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Kvadratinė šaknis"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Prekės ženklas"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Perduoti"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Pažymėti žvaigždute"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Svaras"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Daugtaškis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Apatinės dvigubos kabutės"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Balso įvestis"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Šiuo metu balso įvestis jūsų kompiuteryje nepalaikoma, bet ji veikia anglų k."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Balso įvesčiai naudojamas „Google“ kalbos atpažinimas. Taikoma "<a href="http://m.google.com/privacy">"privatumo politika mobiliesiems"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Jei norite išjungti balso įvestį, eikite į įvesties metodo nustatymus."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Jei norite naudoti balso įvestį, paspauskite mikrofono mygtuką."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Kalbėkite dabar"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Veikia"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Klaida. Bandykite dar kartą."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Nepavyko prijungti"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Klaida, per daug kalbos."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema su garsu"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Serverio klaida"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Negirdima jokia kalba"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Atitikmenų nerasta"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Balso paieška neįdiegta"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Užuomina:"</b>" perbraukite klaviatūra, kad galėtumėte kalbėti"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Užuomina:"</b>" kitą kartą pabandykite sakyti skyrybos ženklų pavadinimus, pvz., „taškas“, „kablelis“ arba „klaustukas“."</string>
-    <string name="cancel" msgid="6830980399865683324">"Atšaukti"</string>
-    <string name="ok" msgid="7898366843681727667">"Gerai"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Įgalintas antrasis lygis"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Įgalintos didžiosios raidės"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Antrasis lygis išjungtas"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Simbolių režimas"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Raidžių režimas"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefono režimas"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefono simbolių režimas"</string>
     <string name="voice_input" msgid="3583258583521397548">"Įvesties balsu klavišas"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Pagr. klaviatūroje"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Simbolių klaviatūr."</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrof. pagr. klav."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrof. simb. klav."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Balso įv. neleidž."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Pasirinkti įvesties metodą"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigūruoti įvesties metodus"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Įvesties kalbos"</string>
     <string name="select_language" msgid="3693815588777926848">"Įvesties kalbos"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Kad išsaugotumėte, dar kartą palieskite"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Žodynas galimas"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Įgalinti naudotojų atsiliepimus"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Padėkite patobulinti šią įvesties metodo redagavimo programą automatiškai „Google“ siųsdami naudojimo statistiką ir strigčių ataskaitas."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Klaviatūros tema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Vokiška QWERTY klaviatūra"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglų k. (JK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglų k. (JAV)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tinkamumo tyrimo režimas"</string>
diff --git a/java/res/values-lv/donottranslate-more-keys.xml b/java/res/values-lv/donottranslate-more-keys.xml
index 77e1c26..d0a4448 100644
--- a/java/res/values-lv/donottranslate-more-keys.xml
+++ b/java/res/values-lv/donottranslate-more-keys.xml
@@ -18,16 +18,89 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ā,à,á,â,ä,æ,ã,å</string>
-    <string name="more_keys_for_e">3,ē,è,é,ê,ë,ę,ė</string>
-    <string name="more_keys_for_i">8,ī,î,ï,ì,í,į</string>
-    <string name="more_keys_for_u">7,ū,û,ü,ù,ú</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
-    <string name="more_keys_for_n">ņ,ñ,ń</string>
-    <string name="more_keys_for_c">č,ç,ć</string>
-    <string name="more_keys_for_r">4,ŗ</string>
-    <string name="more_keys_for_z">ž,ź,ż</string>
-    <string name="more_keys_for_k">ķ</string>
-    <string name="more_keys_for_l">ļ,ł</string>
-    <string name="more_keys_for_g">ģ</string>
+    <!-- U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK -->
+    <string name="more_keys_for_a">&#x0101;,&#x00E0;,&#x00E1;,&#x00E2;,&#x00E3;,&#x00E4;,&#x00E5;,&#x00E6;,&#x0105;</string>
+    <!-- U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+011B: "ě" LATIN SMALL LETTER E WITH CARON -->
+    <string name="more_keys_for_e">&#x0113;,&#x0117;,&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0119;,&#x011B;</string>
+    <!-- U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+0131: "ı" LATIN SMALL LETTER DOTLESS I -->
+    <string name="more_keys_for_i">&#x012B;,&#x012F;,&#x00EC;,&#x00ED;,&#x00EE;,&#x00EF;,&#x0131;</string>
+    <!-- U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_o">&#x00F2;,&#x00F3;,&#x00F4;,&#x00F5;,&#x00F6;,&#x0153;,&#x0151;,&#x00F8;</string>
+    <!-- U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+         U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+         U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE -->
+    <string name="more_keys_for_u">&#x016B;,&#x0173;,&#x00F9;,&#x00FA;,&#x00FB;,&#x00FC;,&#x016F;,&#x0171;</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA -->
+    <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;,&#x015F;</string>
+    <!-- U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+         U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x0146;,&#x00F1;,&#x0144;,&#x0144;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x010D;,&#x00E7;,&#x0107;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+010F: "ď" LATIN SMALL LETTER D WITH CARON -->
+    <string name="more_keys_for_d">&#x010F;</string>
+    <!-- U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+         U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+         U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE -->
+    <string name="more_keys_for_r">&#x0157;,&#x0159;,&#x0155;</string>
+    <!-- U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+         U+0165: "ť" LATIN SMALL LETTER T WITH CARON -->
+    <string name="more_keys_for_t">&#x0163;,&#x0165;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017C;,&#x017A;</string>
+    <!-- U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA -->
+    <string name="more_keys_for_k">&#x0137;</string>
+    <!-- U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+         U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+         U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+         U+013E: "ľ" LATIN SMALL LETTER L WITH CARON -->
+    <string name="more_keys_for_l">&#x013C;,&#x0142;,&#x013A;,&#x013E;</string>
+    <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+         U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
 </resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index af01d92..b406692 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android tastatūra"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android tastatūra (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android tastatūras iestatījumi"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Ievades opcijas"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android korekcija"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android pareizrakstības pārbaudītājs"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android pareizrakstības pārbaudītājs (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Pareizrakstības pārbaudes iestatījumi"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Tuvuma datu izmantošana"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Pareizrakstības pārbaudei izmantojiet tastatūrai līdzīgu tuvuma algoritmu."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Meklēt kontaktp. vārdus"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Pareizrakst. pārbaudītājs lieto ierakstus no kontaktp. saraksta."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrēt, nospiežot taustiņu"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Skaņa, nospiežot taustiņu"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Nospiežot taustiņu, parādīt uznirstošo izvēlni"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Teksta korekcija"</string>
     <string name="misc_category" msgid="6894192814868233453">"Citas opcijas"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Papildu iestatījumi"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opcijas speciālistiem"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opcijas ekspertiem"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Pārsl. uz citām iev. met."</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Valodas pārslēgš. taustiņu var lietot arī citām ievades metodēm."</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Atsp. val. pārsl. taust."</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Taust. uzn. loga noraid. aizk."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez aizkaves"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Noklusējums"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vienmēr rādīt"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Rādīt portreta režīmā"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vienmēr slēpt"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Rādīt iestatījumu taustiņu"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automāt. korekcija"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Atstarpes taustiņš un interpunkcija; automātiska kļūdainu vārdu labošana"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izslēgta"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: saglabāts"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Sākt"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tālāk"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Iepr."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Gatavs"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Sūtīt"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nav ievadīts teksts"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Taustiņu kods %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Pārslēgšanas taustiņš"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Pārslēgšanas taustiņš iespējots"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Burtslēgs iespējots"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Pārslēgšanas taustiņš iespējots (pieskarieties, lai atspējotu)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Burtslēgs iespējots (pieskarieties, lai atspējotu)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Dzēšanas taustiņš"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simboli"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Burti"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Balss ievade"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smaidoša seja"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Ievadīšanas taustiņš"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komats"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punkts"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Kreisā iekava"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Labā iekava"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Kols"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikols"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Izsaukuma zīme"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Jautājuma zīme"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Pēdiņas"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Vienpēdiņas"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punkts"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Kvadrātsakne"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pī"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Preču zīme"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"c/o"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Zvaigznīte"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Cipara simbols"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Daudzpunkte"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Apakšējās pēdiņas"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Balss ievade"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Balss ievade jūsu valodā pašlaik netiek atbalstīta, taču tā ir pieejama angļu valodā."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Balss ievadei tiek izmantota Google runas atpazīšanas funkcija. Uz šīs funkcijas lietošanu attiecas "<a href="http://m.google.com/privacy">"mobilo sakaru ierīču lietošanas konfidencialitātes politika"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Lai izslēgtu balss ievadi, atveriet ievades metodes iestatījumus."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Lai izmantotu balss ievadi, nospiediet mikrofona taustiņu."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Runājiet!"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Notiek apstrāde"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Kļūda. Lūdzu, mēģiniet vēlreiz."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Nevar izveidot savienojumu."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Kļūda, pārāk ilga balss ievade."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Audio problēma"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Servera kļūda"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nekas nav dzirdams."</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nav atrasta neviena atbilstība."</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Balss meklēšana nav instalēta."</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Ieteikums:"</b>" slidiniet pirkstu pār tastatūru, lai veiktu balss ievadi."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Ieteikums:"</b>" nākamreiz mēģiniet izrunāt pieturzīmes, piemēram, “punkts”, “komats” vai “jautājuma zīme”."</string>
-    <string name="cancel" msgid="6830980399865683324">"Atcelt"</string>
-    <string name="ok" msgid="7898366843681727667">"Labi"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Pārslēgšanas režīms iespējots"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Burtslēgs iespējots"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Pārslēgšanas režīms atspējots"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Simbolu režīms"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Burtu režīms"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Tālruņa režīms"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Tālruņa simbolu režīms"</string>
     <string name="voice_input" msgid="3583258583521397548">"Balss ievades atslēga"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Uz galv. tastatūras"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Uz simbolu tastat."</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr.uz galv.tastat."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr.uz simb.tastat."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Balss iev. atspējota"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Atlasīt ievades metodi"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Ievades metožu konfigurēšana"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Ievades valodas"</string>
     <string name="select_language" msgid="3693815588777926848">"Ievades valodas"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Pieskarieties vēlreiz, lai saglabātu"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Pieskarieties vēlreiz, lai saglabātu."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ir pieejama vārdnīca."</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Iespējot lietotāju atsauksmes"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Palīdziet uzlabot šo ievades metodes redaktoru, automātiski nosūtot lietojuma statistiku un pārskatus par avārijām uzņēmumam Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastatūras motīvs"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Vācu valodas QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Angļu valoda (Lielbritānija)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Angļu valoda (ASV)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Lietojamības izpētes režīms"</string>
diff --git a/java/res/values-mk/donottranslate-more-keys.xml b/java/res/values-mk/donottranslate-more-keys.xml
new file mode 100644
index 0000000..d0cccf6
--- /dev/null
+++ b/java/res/values-mk/donottranslate-more-keys.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0455: "ѕ" CYRILLIC SMALL LETTER DZE -->
+    <string name="keylabel_for_south_slavic_row1_6">&#x0455;</string>
+    <!-- U+045C: "ќ" CYRILLIC SMALL LETTER KJE -->
+    <string name="keylabel_for_south_slavic_row2_11">&#x045C;</string>
+    <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE -->
+    <string name="keylabel_for_south_slavic_row3_1">&#x0437;</string>
+    <!-- U+0453: "ѓ" CYRILLIC SMALL LETTER GJE -->
+    <string name="keylabel_for_south_slavic_row3_8">&#x0453;</string>
+    <!-- U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE -->
+    <string name="more_keys_for_cyrillic_ie">&#x0450;</string>
+    <!-- U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE -->
+    <string name="more_keys_for_cyrillic_i">&#x045D;</string>
+    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+    <!-- <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201E;,&#x201C;,&#x201D;,&#x201F;,&#x00AB;,&#x00BB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+</resources>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index d6bf559..71cceff 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -21,12 +21,14 @@
 <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">"Papan kekunci Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Papan kekunci Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Tetapan papan kekunci Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Pembetulan Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Penyemak ejaan Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Penyemak ejaan Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Tetapan penyemakan ejaan"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Gunakan data kehampiran"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Gunakan algoritma kehampiran ala papan kekunci untuk pemeriksaan ejaan"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Penyemak ejaan menggunakan entri dari senarai kenalan anda"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar pada tekanan kekunci"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Bunyi pada tekanan kekunci"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop timbul pada tekanan kunci"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Pembetulan teks"</string>
     <string name="misc_category" msgid="6894192814868233453">"Pilihan lain"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Tetapan terperinci"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Pilihan untuk pengguna pakar"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Pilihan untuk pakar"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Tukar ke kaedah input lain"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Kunci pertukaran bahasa meliputi kaedah masukan lain juga"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Tekan kunci alih bahasa"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Pop tmbl knci ketpkn lengah"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Tiada lengah"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Lalai"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Sentiasa tunjukkan"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Tunjukkan pada mod potret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sentiasa sembunyikan"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Tunjukkan kekunci tetapan"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Auto Pembetulan"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bar ruang dan tanda baca secara automatik membetulkan perkataan yang ditaip salah"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Matikan"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Disimpan"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pergi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seterusnya"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Sblm"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Selesai"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Hantar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Tiada teks dimasukkan"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kod kunci %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift didayakan"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Kunci huruf besar didayakan"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kunci anjak dihidupkan (ketik untuk melumpuhkan)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Kunci huruf besar dihidupkan (ketik untuk melumpuhkan)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Padam"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simbol"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Huruf"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Input suara"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Muka senyum"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Koma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Tempoh"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Tanda kurung kiri"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Tanda kurung kanan"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Titik bertindih"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Koma bertitik"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Tanda seru"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Tanda soal"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Tanda petikan berganda"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Tanda petikan tunggal"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Titik"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Punca kuasa dua"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Tanda dagangan"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Dengan alamat"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Bintang"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Paun"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Elipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Tanda petikan berganda rendah"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Input suara"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Input suara tidak disokong untuk bahasa anda pada masa ini tetapi ia berfungsi dalam bahasa Inggeris."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Input suara menggunakan pengecaman pertuturan Google. "<a href="http://m.google.com/privacy">"Dasar Privasi Mudah Alih"</a>" digunakan."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Untuk mematikan input suara, pergi ke tetapan kaedah input."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Untuk menggunakan input suara, tekan butang mikrofon."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Sebutkan sekarang"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Berfungsi"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Ralat. Sila cuba lagi."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Tidak boleh disambungkan"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Ralat, terlalu banyak pertuturan."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Masalah audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Ralat pelayan"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Tiada pertuturan didengari"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Tiada padanan ditemui"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Carian suara tidak dipasang"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021">"Petunjuk"<b>":"</b>" Leret merentasi papan kekunci untuk bercakap"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Petunjuk:"</b>" Lain kali, cuba ucapkan tanda baca seperti \"titik\", \"koma\" atau \"tanda soal\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Batal"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Kunci anjak didayakan"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Kunci huruf besar didayakan"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Kunci anjak dilumpuhkan"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mod simbol"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mod huruf"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mod telefon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mod simbol telefon"</string>
     <string name="voice_input" msgid="3583258583521397548">"Kunci input suara"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Hidpkn kekunci utama"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Pd ppn k’unci simbol"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mik. pd kekunci utma"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mik. pd kekunci smbl"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Input suara dilmphkn"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Pilih kaedah input"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan kaedah input"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Bahasa input"</string>
     <string name="select_language" msgid="3693815588777926848">"Bahasa input"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Sentuh sekali lagi untuk menyimpan"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Kamus tersedia"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Dayakan maklum balas pengguna"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Bantu memperbaik editor input ini dengan menghantar statistik penggunaan dan laporan runtuhan kepada Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema papan kekunci"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY Bahasa Jerman"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Bahasa Inggeris (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Bahasa Inggeris (AS)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mod kajian kebolehgunaan"</string>
diff --git a/java/res/values-nb/donottranslate-more-keys.xml b/java/res/values-nb/donottranslate-more-keys.xml
index b98341c..49e6d5f 100644
--- a/java/res/values-nb/donottranslate-more-keys.xml
+++ b/java/res/values-nb/donottranslate-more-keys.xml
@@ -18,12 +18,43 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,ä,á,â,ã,ā</string>
-    <string name="more_keys_for_e">3,é,è,ê,ë,ę,ė,ē</string>
-    <string name="more_keys_for_o">9,ô,ò,ó,ö,õ,œ,ō</string>
-    <string name="more_keys_for_u">7,ü,û,ù,ú,ū</string>
-    <string name="keylabel_for_scandinavia_row2_10">ø</string>
-    <string name="keylabel_for_scandinavia_row2_11">æ</string>
-    <string name="more_keys_for_scandinavia_row2_10">ö</string>
-    <string name="more_keys_for_scandinavia_row2_11">ä</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E4;,&#x00E1;,&#x00E2;,&#x00E3;,&#x0101;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F4;,&#x00F2;,&#x00F3;,&#x00F6;,&#x00F5;,&#x0153;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FC;,&#x00FB;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE -->
+    <string name="keylabel_for_nordic_row1_11">&#x00E5;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="keylabel_for_nordic_row2_10">&#x00F8;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="keylabel_for_nordic_row2_11">&#x00E6;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="more_keys_for_nordic_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="more_keys_for_nordic_row2_11">&#x00E4;</string>
 </resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index acd636b..c412ea0 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -21,12 +21,14 @@
 <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">"Skjermtastatur"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-tastatur (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Innstillinger for skjermtastatur"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inndataalternativer"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-stavekontroll"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-stavekontroll"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-stavekontroll (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Innstillinger for stavekontroll"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Bruk nærhetsdata"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Bruk en tastaturlignende algoritme til stavekontroll"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå opp kontaktnavn"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Stavekontrollen bruker oppføringer fra kontaktlisten din"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer ved tastetrykk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetrykk"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Hurtigvindu ved tastetrykk"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Tekstkorrigering"</string>
     <string name="misc_category" msgid="6894192814868233453">"Andre alternativer"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Avanserte innstillinger"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Alternativer for ekspertbrukere"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Alternativer for eksperter"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Bytt inndatametode"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Tasten for språkbytte dekker også andre inndatametoder"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Skjul språkbyttetasten"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Tregt tastevindu"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"U/ forsinkelse"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vis alltid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Vis i stående modus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Skjul alltid"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Vis innstillingsnøkkel"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automatisk retting"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Mellomromstast og skilletegn retter automat. feilstavede ord"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
@@ -62,8 +66,9 @@
     <string name="bigram_prediction" msgid="8914273444762259739">"Bigram-prediksjon"</string>
     <string name="bigram_prediction_summary" msgid="1747261921174300098">"Bruk forrige ord også for forslag"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Lagret"</string>
-    <string name="label_go_key" msgid="1635148082137219148">"Gå"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Utfør"</string>
     <string name="label_next_key" msgid="362972844525672568">"Neste"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Forr."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Utfør"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Send"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ingen tekst er skrevet inn"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tastaturkode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift er aktivert"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock er aktivert"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift er på (trykk for å deaktivere)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock er på (trykk for å deaktivere)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Slett"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboler"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Bokstaver"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Taleinndata"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smilefjes"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punktum"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Venstre parentes"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Høyre parentes"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Kolon"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikolon"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Utropstegn"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Spørsmålstegn"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dobbelt anførselstegn"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Enkelt anførselstegn"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Prikk"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Kvadratrot"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Varemerke"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"c/o"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Stjerne"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Firkant"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipse"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Lavt dobbelt anførselstegn"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Stemmedata"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Stemmedata håndteres foreløpig ikke på ditt språk, men fungerer på engelsk."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Google Voice bruker Googles talegjenkjenning. "<a href="http://m.google.com/privacy">"Personvernreglene for mobil"</a>" gjelder."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Gå til innstillinger for inndatametode for å slå av stemmedata."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Trykk på mikrofonknappen for å aktivere stemmedata."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Snakk nå"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Arbeider"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Feil. Prøv på nytt."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Kunne ikke koble til"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Feil – for mye tale"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Lydproblem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Tjenerfeil"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Ingen tale høres"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Ingen treff"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Talesøk ikke installert"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021">"Hint:"<b>" Sveip over tastaturet for å snakke"</b></string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Hint:"</b>" Neste gang kan du prøve å tale inn tegnsettingen ved for eksempel å si «punktum», «komma» eller «spørsmålstegn»."</string>
-    <string name="cancel" msgid="6830980399865683324">"Avbryt"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift er aktivert"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock er aktivert"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift er deaktivert"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbolmodus"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Bokstavmodus"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Ringemodus"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Ringemodus med symboler"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tast for taleinndata"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"På hovedtastatur"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"På talltastatur"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon på hovedtast."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrofon på talltastatur"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Taleinndata er deaktiv."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Velg inndatametode"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurer inndatametoder"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Inndataspråk"</string>
     <string name="select_language" msgid="3693815588777926848">"Inndataspråk"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"Trykk på nytt for å lagre"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbok tilgjengelig"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktiver brukertilbakemelding"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Ved å sende bruksstatistikk og programstopprapporter til Google automatisk, hjelper du oss med å gjøre redigeringsfunksjonen for denne inndatametoden enda bedre."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturtema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Tysk QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engelsk (Storbritannia)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engelsk (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Nyttighetsmodus"</string>
diff --git a/java/res/values-nl/donottranslate-more-keys.xml b/java/res/values-nl/donottranslate-more-keys.xml
index 49cc419..73768af 100644
--- a/java/res/values-nl/donottranslate-more-keys.xml
+++ b/java/res/values-nl/donottranslate-more-keys.xml
@@ -18,10 +18,49 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">á,ä,â,à,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,é,ë,ê,è,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,í,ï,ì,î,į,ī</string>
-    <string name="more_keys_for_o">9,ó,ö,ô,ò,õ,œ,ø,ō</string>
-    <string name="more_keys_for_u">7,ú,ü,û,ù,ū</string>
-    <string name="more_keys_for_n">ñ,ń</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E4;,&#x00E2;,&#x00E0;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00EB;,&#x00EA;,&#x00E8;,&#x0119;,&#x0117;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+0133: "ij" LATIN SMALL LIGATURE IJ -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EF;,&#x00EC;,&#x00EE;,&#x012F;,&#x012B;,&#x0133;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F6;,&#x00F4;,&#x00F2;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x00FB;,&#x00F9;,&#x016B;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <!-- U+0133: "ij" LATIN SMALL LIGATURE IJ -->
+    <string name="more_keys_for_y">&#x0133;</string>
 </resources>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 7f0ff7e..8f33aa9 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android-toetsenbord"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-toetsenbord (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Instellingen voor Android-toetsenbord"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropties"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-spellingcontrole"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Spellingcontrole van Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Spellingcontrole van Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Instellingen voor spellingcontrole"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Nabije toetsinfo gebr."</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Algoritme voor nabije toetsen gebruiken voor spellingcontrole"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Contactnamen opzoeken"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"De spellingcontrole gebruikt items uit uw contactenlijst"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Trillen bij toetsaanslag"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Geluid bij toetsaanslag"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up bij toetsaanslag"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Tekstcorrectie"</string>
     <string name="misc_category" msgid="6894192814868233453">"Andere opties"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Geavanceerde instellingen"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opties voor ervaren gebruikers"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opties voor experts"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Invoermeth. overschakelen"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Schakelknop voor taal ook van toepassing op andere invoermethoden"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Taal schakelen onderdr."</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Afwijz.vertr. toetspop-up"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Geen vertraging"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standaard"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Altijd weergeven"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Weergeven in staande modus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Altijd verbergen"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Instellingscode weergeven"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autocorrectie"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Met spatiebalk en interpunctie worden verkeerd gespelde woorden automatisch gecorrigeerd"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Uitgeschakeld"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: opgeslagen"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Start"</string>
     <string name="label_next_key" msgid="362972844525672568">"Verder"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Vorig"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Gereed"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Zenden"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Geen tekst ingevoerd"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Toetscode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift ingeschakeld"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock ingeschakeld"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aan (tik om uit te schakelen)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock aan (tik om uit te schakelen)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Verwijderen"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbolen"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letters"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Spraakinvoer"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley-gezichtje"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punt"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Linkerhaakje"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Rechterhaakje"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dubbele punt"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Puntkomma"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Uitroepteken"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Vraagteken"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dubbele aanhalingstekens"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Enkel aanhalingsteken"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Stip"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Vierkantswortel"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Handelsmerk"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Ten attentie van"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Ster"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Hekje"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Weglatingsteken"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Lage dubbele aanhalingstekens"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Spraakinvoer"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Spraakinvoer wordt momenteel niet ondersteund in uw taal, maar is wel beschikbaar in het Engels."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Spraakinvoer maakt gebruik van de spraakherkenning van Google. Het "<a href="http://m.google.com/privacy">"Privacybeleid van Google Mobile"</a>" is van toepassing."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Als u spraakinvoer wilt uitschakelen, gaat u naar de instellingen voor invoermethoden."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Druk op de microfoontoets om spraakinvoer te gebruiken."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Nu spreken"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Wordt uitgevoerd"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Fout. Probeer het opnieuw."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Kan geen verbinding maken"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Fout, te lange spraakinvoer."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Audioprobleem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Serverfout"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Geen spraak te horen"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Geen resultaten gevonden"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Spraakgestuurd zoeken is niet geïnstalleerd"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Hint:"</b>" schuif over het toetsenbord om te spreken"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Hint:"</b>" spreek de volgende keer interpunctie uit, zoals \'period\' (punt), \'comma\' (komma) of \'question mark\' (vraagteken)."</string>
-    <string name="cancel" msgid="6830980399865683324">"Annuleren"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ingeschakeld"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock ingeschakeld"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift uitgeschakeld"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbolen"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Alfanumeriek toetsenbord"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Toetsenbord telefoon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefoonsymbolen"</string>
     <string name="voice_input" msgid="3583258583521397548">"Sleutel voor spraakinvoer"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Op hoofdtoetsenbord"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Op symbooltoetsenb."</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic op hoofdtoetsb."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic op symb.toetsb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Spraakinvoer is uit"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Invoermethode selecteren"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Invoermethoden configureren"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Invoertalen"</string>
     <string name="select_language" msgid="3693815588777926848">"Invoertalen"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Raak nogmaals aan om op te slaan"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Raak nogmaals aan om op te slaan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordenboek beschikbaar"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Gebruikersfeedback inschakelen."</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Help deze invoermethode te verbeteren door automatisch gebruiksstatistieken en crashmeldingen naar Google te verzenden."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Toetsenbordthema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Duits QWERTY-toetsenbord"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engels (GB)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modus voor gebruiksvriendelijkheidsonderzoek"</string>
diff --git a/java/res/values-pl/donottranslate-more-keys.xml b/java/res/values-pl/donottranslate-more-keys.xml
index 18e1499..0f8a59b 100644
--- a/java/res/values-pl/donottranslate-more-keys.xml
+++ b/java/res/values-pl/donottranslate-more-keys.xml
@@ -18,12 +18,48 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ą,á,à,â,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,ę,è,é,ê,ë,ė,ē</string>
-    <string name="more_keys_for_o">9,ó,ö,ô,ò,õ,œ,ø,ō</string>
-    <string name="more_keys_for_s">ś,ß,š</string>
-    <string name="more_keys_for_n">ń,ñ</string>
-    <string name="more_keys_for_c">ć,ç,č</string>
-    <string name="more_keys_for_z">ż,ź,ž</string>
-    <string name="more_keys_for_l">ł</string>
+    <!-- U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x0105;,&#x00E1;,&#x00E0;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
+    <!-- U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+    <string name="more_keys_for_e">&#x0119;,&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0117;,&#x0113;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F6;,&#x00F4;,&#x00F2;,&#x00F5;,&#x0153;,&#x00F8;,&#x014D;</string>
+    <!-- U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x015B;,&#x00DF;,&#x0161;</string>
+    <!-- U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+         U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
+    <string name="more_keys_for_n">&#x0144;,&#x00F1;</string>
+    <!-- U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x0107;,&#x00E7;,&#x010D;</string>
+    <!-- U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+         U+017E: "ž" LATIN SMALL LETTER Z WITH CARON -->
+    <string name="more_keys_for_z">&#x017C;,&#x017A;,&#x017E;</string>
+    <!-- U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
+    <string name="more_keys_for_l">&#x0142;</string>
 </resources>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index d737ebd..ea358c0 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -21,12 +21,14 @@
 <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">"Klawiatura Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Klawiatura Androida (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Ustawienia klawiatury Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcje wprowadzania"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Korekta Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Sprawdzanie pisowni na Androidzie"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Sprawdzanie pisowni na Androidzie (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Ustawienia sprawdzania pisowni"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Użyj danych o klawiszach"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Przy sprawdzaniu pisowni używaj algorytmu uwzględniającego położenie klawiszy na klawiaturze"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Przeszukaj nazwy kontaktów"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Sprawdzanie pisowni bierze pod uwagę wpisy z listy kontaktów."</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Powiększ po naciśnięciu"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Poprawianie tekstu"</string>
     <string name="misc_category" msgid="6894192814868233453">"Inne opcje"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Ustawienia zaawansowane"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opcje dla zaawansowanych użytkowników"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opcje dla ekspertów"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Włącz inne metody wprowadzania"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Klawisz zmiany języka obejmuje też inne metody wprowadzania"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Wyłącz klawisz zmiany języka"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Opóźnienie znikania klawiszy"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez opóźnienia"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Wartość domyślna"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Zawsze pokazuj"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Pokaż w trybie pionowym"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Zawsze ukrywaj"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Pokaż klawisz ustawień"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autokorekta"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacja i znaki przestankowe poprawiają błędnie wpisane słowa"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Wyłącz"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Zapisano"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Dalej"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Wstecz"</string>
     <string name="label_done_key" msgid="2441578748772529288">"OK"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Wyślij"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nie wprowadzono tekstu"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kod klawisza: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift włączony"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock włączony"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift włączony (kliknij, by wyłączyć)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock włączony (kliknij, by wyłączyć)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Usuń"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbole"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Litery"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Wprowadzanie głosowe"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Uśmiechnięta buźka"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Przecinek"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Kropka"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Lewy nawias"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Prawy nawias"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dwukropek"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Średnik"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Wykrzyknik"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Pytajnik"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Cudzysłów podwójny"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Cudzysłów pojedynczy"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punkt"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Pierwiastek kwadratowy"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Znak towarowy"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Znak „przez grzeczność”"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Gwiazdka"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Krzyżyk"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Wielokropek"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Cudzysłów podwójny dolny"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Wprowadzanie głosowe"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Wprowadzanie głosowe obecnie nie jest obsługiwane w Twoim języku, ale działa w języku angielskim."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Funkcja wprowadzania głosowego wykorzystuje mechanizm rozpoznawania mowy. Obowiązuje "<a href="http://m.google.com/privacy">"Polityka prywatności w usługach mobilnych"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Aby wyłączyć rozpoznawanie mowy, przejdź do ustawień sposobu wprowadzania tekstu."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Aby użyć wprowadzania głosowego, naciśnij przycisk mikrofonu."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Mów teraz"</string>
-    <string name="voice_working" msgid="6666937792815731889">"W toku"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Błąd. Spróbuj ponownie."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Nie można nawiązać połączenia"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Błąd, zbyt długa wypowiedź."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problem z dźwiękiem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Błąd serwera"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nie wykryto mowy"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Brak wyników"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Wyszukiwanie głosowe nie jest zainstalowane"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Wskazówka:"</b>" przesuń palcem po klawiaturze, aby mówić."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Wskazówka:"</b>" następnym razem spróbuj wypowiadać nazwy znaków interpunkcyjnych: „kropka”, „przecinek” lub „pytajnik”."</string>
-    <string name="cancel" msgid="6830980399865683324">"Anuluj"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift włączony"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock włączony"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift wyłączony"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Tryb symboli"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Tryb liter"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Tryb telefonu"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Tryb symboli telefonu"</string>
     <string name="voice_input" msgid="3583258583521397548">"Klawisz rozpoznawania mowy"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Na klawiaturze głównej"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Na klawiaturze z symbolami"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon na klawiaturze głównej"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrofon na klawiaturze z symbolami"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Wprowadzanie głosowe jest wyłączone"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Wybierz sposób wprowadzania tekstu"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfiguruj metody wprowadzania"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Języki wprowadzania"</string>
     <string name="select_language" msgid="3693815588777926848">"Języki wprowadzania"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Dotknij ponownie, aby zapisać"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Słownik dostępny"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Włącz przesyłanie opinii użytkownika"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Pomóż ulepszyć edytor wprowadzania tekstu, automatycznie wysyłając do Google statystyki użycia i raporty o awariach."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Motyw klawiatury"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Niemiecka QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Angielska (Wielka Brytania)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Angielska (Stany Zjednoczone)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tryb badania przydatności"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 0a30235..7a0c6c2 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -21,12 +21,14 @@
 <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">"Teclado do Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Definições de teclado do Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de introdução"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correção do Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Verificador ortográfico do Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Verificador ortográfico do Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Definições da verificação ortográfica"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Utilizar dados de prox."</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Util. algoritmo de prox. semelhante a teclado para verif.  ortog."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Procurar nomes de contac."</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"O corretor ortográfico utiliza entradas da sua lista de contactos"</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Mostrar popup ao premir tecla"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Correção de texto"</string>
     <string name="misc_category" msgid="6894192814868233453">"Outras opções"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Definições avançadas"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opções para utilizadores experientes"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opções para especialistas"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Mudar p/ outros mét. ent."</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"A tecla de mudança de idioma abrange outros métodos de entrada"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Suprimir tecla mud idioma"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Atraso p/ ignorar pop-up"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sem atraso"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predefinido"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar no modo de retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar sempre"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla das definições"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Auto correcção"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Correcção automática de palavras mal escritas c/ barra de espaços e pontuação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desligar"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Ant."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Feito"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nenhum texto digitado"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Código da tecla %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift ativado"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock ativado"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ativado (tocar para desativar)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock ativado (tocar para desativar)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbolos"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letras"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Entrada de voz"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Cara sorridente"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Vírgula"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Ponto final"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Parêntese esquerdo"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Parêntese direito"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dois pontos"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Ponto e vírgula"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Ponto de exclamação"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Ponto de interrogação"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Aspas"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Plica"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Ponto"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Raiz quadrada"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marca comercial"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Ao cuidado de"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Marcar com estrela"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Cardinal"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Reticências"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Aspas duplas baixas"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Entrada de voz"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualmente, a entrada de voz não é suportada para o seu idioma, mas funciona em inglês."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A entrada de voz utiliza o reconhecimento de voz da Google. É aplicável a "<a href="http://m.google.com/privacy">"Política de privacidade do Google Mobile"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Para desactivar a entrada de voz, aceda às definições do método de entrada."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Para utilizar a entrada de voz, prima o botão do microfone."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Falar agora"</string>
-    <string name="voice_working" msgid="6666937792815731889">"A executar"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Erro. Tente novamente."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Não foi possível ligar"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Erro, discurso demasiado longo."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema de áudio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Erro no servidor"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nenhuma voz ouvida"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Não foram encontradas correspondências"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Pesquisa de voz não instalada"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Sugestão:"</b>" Deslize no teclado para falar"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Sugestão:"</b>" Da próxima vez, experimente dizer a pontuação como \"ponto final\", \"vírgula\" ou \"ponto de interrogação\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Cancelar"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ativado"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock ativado"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift desativado"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Modo de símbolos"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Modo de letras"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Modo de telemóvel"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Modo de símbolos de telemóvel"</string>
     <string name="voice_input" msgid="3583258583521397548">"Chave de entrada de voz"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"No teclado principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"No teclado símbolos"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic. tecl. principal"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic. tecl. símbolos"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entr. voz desact."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Selecionar método de entrada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de introdução"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas de entrada"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de introdução"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Toque novamente para guardar"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activar comentários do utilizador"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Envie automaticamente estatísticas de utilização e relatórios de falhas para a Google e ajude-nos a melhorar este editor de método de introdução."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY Alemão"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglês (RU)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglês (EUA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudo da capacidade de utilização"</string>
diff --git a/java/res/values-pt/donottranslate-more-keys.xml b/java/res/values-pt/donottranslate-more-keys.xml
index 31d9417..0c9065f 100644
--- a/java/res/values-pt/donottranslate-more-keys.xml
+++ b/java/res/values-pt/donottranslate-more-keys.xml
@@ -18,10 +18,48 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">á,ã,à,â,ä,å,æ,ª</string>
-    <string name="more_keys_for_e">3,é,ê,è,ę,ė,ē,ë</string>
-    <string name="more_keys_for_i">8,í,î,ì,ï,į,ī</string>
-    <string name="more_keys_for_o">9,ó,õ,ô,ò,ö,œ,ø,ō,º</string>
-    <string name="more_keys_for_u">7,ú,ü,ù,û,ū</string>
-    <string name="more_keys_for_c">ç,č,ć</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E3;,&#x00E0;,&#x00E2;,&#x00E4;,&#x00E5;,&#x00E6;,&#x00AA;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00EA;,&#x00E8;,&#x0119;,&#x0117;,&#x0113;,&#x00EB;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EE;,&#x00EC;,&#x00EF;,&#x012F;,&#x012B;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+    <string name="more_keys_for_o">&#x00F3;,&#x00F5;,&#x00F4;,&#x00F2;,&#x00F6;,&#x0153;,&#x00F8;,&#x014D;,&#x00BA;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x00F9;,&#x00FB;,&#x016B;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x00E7;,&#x010D;,&#x0107;</string>
 </resources>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 36a70da..9a0cf30 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -21,12 +21,14 @@
 <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">"Teclado Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Configurações de teclado Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de entrada"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correção do Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corretor ortográfico do Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corretor ortográfico do Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Configurações de verificação ortográfica"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Usar dados de proximidade"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Usar algoritmo de prox. tipo teclado para verificação ortográfica"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nomes de contatos"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"O corretor ortográfico usa entradas de sua lista de contatos"</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Exibir pop-up ao digitar"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Correção de texto"</string>
     <string name="misc_category" msgid="6894192814868233453">"Outras opções"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Configurações avançadas"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opções para usuários experientes"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opções para especialistas"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Outros métodos de entrada"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"A tecla p/ mudar o idioma também cobre outros métodos de entrada"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Ocult. tecla mudar idioma"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Dispens. atraso chave princ."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sem atraso"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Padrão"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar em modo retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sempre ocultar"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla de config."</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autocorreção"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"A barra de espaço e a pontuação corrigem automaticamente palavras com erro de digitação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desativado"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Salvo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Volt."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Feito"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nenhum texto digitado"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Código de tecla %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift ativado"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock ativado"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ativado (toque para desativar)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock ativado (toque para desativar)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Excluir"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbolos"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letras"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Entrada de voz"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Carinha sorridente"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Voltar"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Vírgula"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Ponto final"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Parêntese esquerdo"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Parêntese direito"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dois pontos"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Ponto e vírgula"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Ponto de exclamação"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Ponto de interrogação"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Aspa dupla"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Aspa simples"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Ponto"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Raiz quadrada"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marca registrada"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Porcentagem"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Asterisco"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Sustenido"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Reticências"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Aspas duplas inferiores"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Entrada de voz"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"A entrada de voz não é suportada no momento para o seu idioma, mas funciona em inglês."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A entrada de texto por voz usa o reconhecimento de voz do Google. "<a href="http://m.google.com/privacy">"A política de privacidade para celulares"</a>" é aplicada."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Para desativar a entrada de texto por voz, vá para configurações do método de entrada."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Para usar a entrada de texto por voz, pressione o botão do microfone."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Fale agora"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Trabalhando"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Erro. Tente novamente."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Não foi possível conectar"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Erro, fala muito longa."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema com o áudio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Erro do servidor"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nenhuma fala ouvida"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Não há resultados compatíveis"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"A pesquisa por voz não está instalada"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Dica:"</b>" Deslize sobre o teclado para falar"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Dica:"</b>" Da próxima vez, tente falar o nome da pontuação como \"ponto\", \"vírgula\" ou \"ponto de interrogação\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Cancelar"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ativado"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock ativado"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift desativado"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Modo de símbolos"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Modo de cartas"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Modo de telefone"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Modo de símbolos de telefone"</string>
     <string name="voice_input" msgid="3583258583521397548">"Chave de entrada de texto por voz"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"No teclado principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"No teclado de símb."</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic. no teclado"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic. no teclado"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Texto por voz desat."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Selecionar método de entrada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas de entrada"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Toque novamente para salvar"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para salvar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ativar comentário do usuário"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Ajude a melhorar este editor de método de entrada enviando automaticamente ao Google estatísticas de uso e relatórios de falhas."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY alemão"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglês (Reino Unido)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglês (EUA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudo de utilização"</string>
diff --git a/java/res/values-rm/donottranslate-more-keys.xml b/java/res/values-rm/donottranslate-more-keys.xml
index ea9a559..aa0d7f8 100644
--- a/java/res/values-rm/donottranslate-more-keys.xml
+++ b/java/res/values-rm/donottranslate-more-keys.xml
@@ -18,5 +18,12 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_o">9,ò,ó,ö,ô,õ,œ,ø</string>
+    <!-- U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_o">&#x00F2;,&#x00F3;,&#x00F6;,&#x00F4;,&#x00F5;,&#x0153;,&#x00F8;</string>
 </resources>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index ad35da8..bacb7d4 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -21,16 +21,20 @@
 <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>
+    <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
+    <skip />
     <string name="english_ime_settings" msgid="6661589557206947774">"Parameters da la tastatura Android"</string>
     <!-- no translation found for english_ime_input_options (3909945612939668554) -->
     <skip />
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
+    <skip />
+    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
     <skip />
     <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
     <skip />
-    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
     <skip />
-    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
     <skip />
     <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>
@@ -43,7 +47,13 @@
     <skip />
     <!-- no translation found for advanced_settings (362895144495591463) -->
     <skip />
-    <!-- no translation found for advanced_settings_summary (5193513161106637254) -->
+    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
+    <skip />
+    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
+    <skip />
+    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
+    <skip />
+    <!-- no translation found for suppress_language_switch_key (8003788410354806368) -->
     <skip />
     <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
     <skip />
@@ -74,8 +84,6 @@
     <skip />
     <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
     <skip />
-    <!-- no translation found for prefs_settings_key (4623341240804046498) -->
-    <skip />
     <!-- no translation found for auto_correction (4979925752001319458) -->
     <skip />
     <!-- no translation found for auto_correction_summary (5625751551134658006) -->
@@ -98,6 +106,8 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Memorisà"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Dai"</string>
     <string name="label_next_key" msgid="362972844525672568">"Vinavant"</string>
+    <!-- no translation found for label_previous_key (1211868118071386787) -->
+    <skip />
     <string name="label_done_key" msgid="2441578748772529288">"Finì"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Trametter"</string>
     <!-- no translation found for label_to_alpha_key (4793983863798817523) -->
@@ -120,9 +130,9 @@
     <skip />
     <!-- no translation found for spoken_description_shift (244197883292549308) -->
     <skip />
-    <!-- no translation found for spoken_description_shift_shifted (954941524766465022) -->
+    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
     <skip />
-    <!-- no translation found for spoken_description_caps_lock (5660626444912131764) -->
+    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
     <skip />
     <!-- no translation found for spoken_description_delete (8740376944276199801) -->
     <skip />
@@ -144,69 +154,22 @@
     <skip />
     <!-- no translation found for spoken_description_return (8178083177238315647) -->
     <skip />
-    <!-- no translation found for spoken_description_comma (4970844442999724586) -->
-    <skip />
-    <!-- no translation found for spoken_description_period (5286614628077903945) -->
-    <skip />
-    <!-- no translation found for spoken_description_left_parenthesis (8524822120595052415) -->
-    <skip />
-    <!-- no translation found for spoken_description_right_parenthesis (1085757995851933164) -->
-    <skip />
-    <!-- no translation found for spoken_description_colon (4312420908484277077) -->
-    <skip />
-    <!-- no translation found for spoken_description_semicolon (37737920987155179) -->
-    <skip />
-    <!-- no translation found for spoken_description_exclamation_mark (2625684427460737157) -->
-    <skip />
-    <!-- no translation found for spoken_description_question_mark (7074097784255379666) -->
-    <skip />
-    <!-- no translation found for spoken_description_double_quote (5485320575389905967) -->
-    <skip />
-    <!-- no translation found for spoken_description_single_quote (4451320362665463938) -->
-    <skip />
     <!-- no translation found for spoken_description_dot (40711082435231673) -->
     <skip />
-    <!-- no translation found for spoken_description_square_root (190595160284757811) -->
+    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
     <skip />
-    <!-- no translation found for spoken_description_pi (4554418247799952239) -->
+    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
     <skip />
-    <!-- no translation found for spoken_description_delta (3607948313655721579) -->
+    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
     <skip />
-    <!-- no translation found for spoken_description_trademark (475877774077871369) -->
+    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
     <skip />
-    <!-- no translation found for spoken_description_care_of (7492800237237796530) -->
+    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
     <skip />
-    <!-- no translation found for spoken_description_star (1009742725387231977) -->
+    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
     <skip />
-    <!-- no translation found for spoken_description_pound (5530577649206922631) -->
+    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
     <skip />
-    <!-- no translation found for spoken_description_ellipsis (1687670869947652062) -->
-    <skip />
-    <!-- no translation found for spoken_description_low_double_quote (3551394572784840975) -->
-    <skip />
-    <string name="voice_warning_title" msgid="4419354150908395008">"Cumonds vocals"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"\"Cumonds vocals en Vossa lingua na vegnan actualmain betg sustegnids, ma la funcziun è disponibla per englais.\""</string>
-    <!-- no translation found for voice_warning_may_not_understand (5596289095878251072) -->
-    <skip />
-    <!-- no translation found for voice_warning_how_to_turn_off (3190378129944934856) -->
-    <skip />
-    <!-- no translation found for voice_hint_dialog_message (1420686286820661548) -->
-    <skip />
-    <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>
     <!-- no translation found for voice_input (3583258583521397548) -->
     <skip />
     <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
@@ -221,22 +184,18 @@
     <skip />
     <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
     <skip />
-    <!-- no translation found for selectInputMethod (315076553378705821) -->
-    <skip />
     <!-- no translation found for configure_input_method (373356270290742459) -->
     <skip />
     <string name="language_selection_title" msgid="1651299598555326750">"Linguas da cumonds vocals"</string>
     <!-- no translation found for select_language (3693815588777926848) -->
     <skip />
-    <!-- no translation found for hint_add_to_dictionary (9006292060636342317) -->
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
     <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Dicziunari disponibel"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activar il feedback da l\'utilisader"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Gidai a meglierar quest editur da la metoda d\'endataziun cun trametter automaticamain datas statisticas davart l\'utilisaziun e rapports da collaps a Google."</string>
     <!-- no translation found for keyboard_layout (8451164783510487501) -->
     <skip />
-    <!-- no translation found for subtype_de_qwerty (3358900499589259491) -->
-    <skip />
     <!-- no translation found for subtype_en_GB (88170601942311355) -->
     <skip />
     <!-- no translation found for subtype_en_US (6160452336634534239) -->
diff --git a/java/res/values-ro/donottranslate-more-keys.xml b/java/res/values-ro/donottranslate-more-keys.xml
index d7e6a17..44613cf 100644
--- a/java/res/values-ro/donottranslate-more-keys.xml
+++ b/java/res/values-ro/donottranslate-more-keys.xml
@@ -18,8 +18,28 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ă,â,à,á,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_i">8,î,ï,ì,í,į,ī</string>
-    <string name="more_keys_for_s">ș,ß,ś,š</string>
-    <string name="more_keys_for_t">5,ț</string>
+    <!-- U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
+    <string name="more_keys_for_a">&#x00E2;,&#x00E3;,&#x0103;,&#x00E0;,&#x00E1;,&#x00E4;,&#x00E6;,&#x00E5;,&#x0101;</string>
+    <!-- U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x00EE;,&#x00EF;,&#x00EC;,&#x00ED;,&#x012F;,&#x012B;</string>
+    <!-- U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x0219;,&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW -->
+    <string name="more_keys_for_t">&#x021B;</string>
 </resources>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index c1dea54..a338051 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -21,12 +21,14 @@
 <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">"Tastatură Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tastatură Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Setările tastaturii Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opţiuni de introducere text"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Corecţie Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Verificator ortografic Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Verificator ortografic Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Setări de verificare ortografică"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Utiliz. datele de proxim."</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Utilizaţi un algor. de prox. similar tastat. pt. verif. ortograf."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Verificare nume în agendă"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Verificatorul ortografic utilizează intrări din lista de contacte"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrare la apăsarea tastei"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sunet la apăsarea tastei"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Fereastră pop-up la apăsarea tastei"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Corectare text"</string>
     <string name="misc_category" msgid="6894192814868233453">"Alte opţiuni"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Setări avansate"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Opţiuni pt. utiliz. experţi"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opţiuni pentru experţi"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Comut. alte metode de introd."</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Tasta de comutare între limbi include şi alte metode de introd."</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Suprim. tasta comut. limbi"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Înt. înch. pop-up esenţ."</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Fără întârziere"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Prestabilit"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Afişaţi întotdeauna"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Afişaţi în modul Portret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ascundeţi întotdeauna"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Afişaţi tasta setări"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autocorecţie"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corectare automată cuvinte prin bară spaţiu/semne punctuaţie"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Dezactivată"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: salvat"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Înainte"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Înapoi"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Terminat"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Trimiteţi"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nu a fost introdus text"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tasta cu codul %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift activat"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock activat"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tasta Shift este activată (apăsaţi pentru a o dezactiva)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Tasta Caps Lock este activată (apăsaţi pentru a o dezactiva)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simboluri"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Litere"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Intrare vocală"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Faţă zâmbitoare"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Virgulă"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punct"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Paranteză închisă"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Paranteză deschisă"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Două puncte"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Punct şi virgulă"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Semn de exclamaţie"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Semn de întrebare"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Ghilimele duble"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Ghilimele simple"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punct"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Rădăcină pătrată"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Marcă comercială"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"În atenţia"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Stea"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Diez"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Puncte de suspensie"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Ghilimele duble de deschidere"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Intrare voce"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Intrarea vocală nu este acceptată în prezent pentru limba dvs., însă funcţionează în limba engleză."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Intrarea vocală utilizează funcţia Google de recunoaştere vocală. Se aplică "<a href="http://m.google.com/privacy">"Politica de confidenţialitate Google Mobil"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Pentru a dezactiva intrarea vocală, accesaţi setările metodei de intrare."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Pentru a utiliza intrarea vocală, apăsaţi pe butonul Microfon."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Vorbiţi acum"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Se analizează"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Eroare. Încercaţi din nou."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Conectare imposibilă"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Eroare, discurs prea lung."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problemă audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Eroare de server"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nu s-a auzit vorbirea"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nicio potrivire"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Căutarea vocală nu este instalată"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Indiciu:"</b>" glisaţi de-a lungul tastaturii pentru a vorbi"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Indiciu:"</b>" data viitoare, încercaţi să rostiţi şi punctuaţia, cum ar fi „punct”, „virgulă”, sau „semn de întrebare”."</string>
-    <string name="cancel" msgid="6830980399865683324">"Anulaţi"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Tasta Shift a fost activată"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Tasta Caps Lock a fost activată"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Tasta Shift a fost dezactivată"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Modul Simboluri"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Modul Alfanumeric"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Modul Telefon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Modul Telefon cu simboluri"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tastă pentru intrarea vocală"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Pe tastat. princip."</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Pe tastat. simbol."</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic. pe tast. princ."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micr. pe tast. simb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Intr. vocală dezact."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Selectaţi metoda de introducere a textului"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configuraţi metodele de intrare"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Selectaţi limba"</string>
     <string name="select_language" msgid="3693815588777926848">"Limbi de intrare"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Atingeţi din nou pentru a salva"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Dicţionar disponibil"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activaţi feedback de la utilizatori"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Ajutaţi la îmbunătăţirea acestui instrument de editare a metodelor de introducere a textului trimiţând în mod automat la Google statistici de utilizare şi rapoarte de blocare."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Temă pentru tastatură"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Tastatură germană QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engleză (Marea Britanie)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engleză (S.U.A.)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modul Studiu privind utilizarea"</string>
diff --git a/java/res/values-ru/donottranslate-more-keys.xml b/java/res/values-ru/donottranslate-more-keys.xml
index f7e006e..0bb5707 100644
--- a/java/res/values-ru/donottranslate-more-keys.xml
+++ b/java/res/values-ru/donottranslate-more-keys.xml
@@ -18,7 +18,16 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_cyrillic_e">5,ё</string>
-    <string name="more_keys_for_cyrillic_soft_sign">ъ</string>
-    <string name="more_keys_for_cyrillic_ha">ъ</string>
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
+    <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
+    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <string name="keylabel_for_east_slavic_row3_5">&#x0438;</string>
+    <!-- U+0451: "ё" CYRILLIC SMALL LETTER IO -->
+    <string name="more_keys_for_cyrillic_ye">&#x0451;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_ha">&#x044A;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
 </resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 855e95e..3ec5daf 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -21,12 +21,14 @@
 <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">"Клавиатура Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавиатура Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Клавиатура Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Настройки"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Исправления Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Проверка правописания Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Проверка правописания Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Настройка проверки правописания"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Алгоритм близости клавиш"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Использовать алгоритм близости клавиш для проверки правописания"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Поиск контактов"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Обращаться к списку контактов при проверке правописания"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Виброотклик клавиш"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук клавиш"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Увеличение нажатых"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Исправление текста"</string>
     <string name="misc_category" msgid="6894192814868233453">"Другие варианты"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Расширенные настройки"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Для опытных пользователей"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Для опытных пользователей"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Другой способ ввода"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Клавиша переключения языков также служит для смены способа ввода"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Блок. кл. перекл. языков"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Задержка закрытия"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без задержки"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По умолчанию"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Всегда показывать"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Показать вертикально"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Всегда скрывать"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Кнопка настроек"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Автоисправление"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Автоматическое исправление опечаток при вводе знака препинания или пробела"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Откл."</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: сохранено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Поиск"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далее"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Пред."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Отправить"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"АБВ"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст не введен"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Код клавиши:%d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Клавиша верхнего регистра"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Верхний регистр включен"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Включена фиксация верхнего регистра"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Верхний регистр включен (нажмите, чтобы отключить)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock включен (нажмите, чтобы отключить)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Клавиша удаления"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Клавиша символов"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Буквы"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Голосовой ввод"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Смайлик"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Клавиша \"Ввод\""</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Запятая"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Точка"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Открывающая скобка"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Закрывающая скобка"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Двоеточие"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Точка с запятой"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Восклицательный знак"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Вопросительный знак"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Двойная кавычка"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Одинарные кавычки"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Точка"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Квадратный корень"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Число \"пи\""</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Дельта"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Товарный знак"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Знак процента"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Пометить"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Английский фунт"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Многоточие"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Нижние двойные кавычки"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Голосовой ввод"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"В настоящее время функция голосового ввода не поддерживает ваш язык, но вы можете пользоваться ей на английском."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Голосовой ввод использует алгоритмы распознавания речи Google. Действует "<a href="http://m.google.com/privacy">"политика конфиденциальности для мобильных устройств"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Функция голосового ввода отключается в настройках способа ввода."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Чтобы использовать голосовой ввод, нажмите кнопку микрофона."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Говорите"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Обработка запроса"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Ошибка. Повторите попытку."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Ошибка подключения"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Слишком длинная фраза"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Неполадка со звуком"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Ошибка сервера"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Речи не слышно"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Ничего не найдено"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Голосовой поиск не установлен"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Совет"</b>". Проведите пальцем по клавиатуре для голосового ввода."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Совет"</b>". В следующий раз проговаривайте знаки препинания, например \"точка\", \"запятая\", \"вопросительный знак\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Отмена"</string>
-    <string name="ok" msgid="7898366843681727667">"ОК"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Верхний регистр включен"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock включен"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Верхний регистр отключен"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Режим добавления символов"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Режим ввода текста"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Режим набора номера"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Режим телефонных символов"</string>
     <string name="voice_input" msgid="3583258583521397548">"Ключ голосового ввода"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Значок на основной клавиатуре"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Значок на клавиатуре символов"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Значок на основной клавиатуре"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Значок на клавиатуре символов"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голосовой ввод откл."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Выбрать способ ввода"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Настройка способов ввода"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Языки ввода"</string>
     <string name="select_language" msgid="3693815588777926848">"Языки ввода"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Нажмите, чтобы сохранить"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Доступен словарь"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Включить отправку сведений"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Помогите усовершенствовать редактор способа ввода, разрешив отправку статистики и отчетов о сбоях в Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема клавиатуры"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Немецкая клавиатура QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"английский (Великобритания)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"английский (США)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим проверки удобства использования"</string>
diff --git a/java/res/values-sk/donottranslate-more-keys.xml b/java/res/values-sk/donottranslate-more-keys.xml
index b73db0a..f6e1e8d 100644
--- a/java/res/values-sk/donottranslate-more-keys.xml
+++ b/java/res/values-sk/donottranslate-more-keys.xml
@@ -18,18 +18,90 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ä,á,à,â,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,é,ě,è,ê,ë,ę,ė,ē</string>
-    <string name="more_keys_for_i">8,í,î,ï,ì,į,ī</string>
-    <string name="more_keys_for_o">9,ô,ó,ö,ò,õ,œ,ø,ō</string>
-    <string name="more_keys_for_u">7,ú,ú,û,ü,ù,ū</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
-    <string name="more_keys_for_n">ň,ñ,ń</string>
-    <string name="more_keys_for_c">č,ç,ć</string>
-    <string name="more_keys_for_y">6,ý,ÿ</string>
-    <string name="more_keys_for_d">ď</string>
-    <string name="more_keys_for_r">4,ŕ,ř</string>
-    <string name="more_keys_for_t">5,ť</string>
-    <string name="more_keys_for_z">ž,ź,ż</string>
-    <string name="more_keys_for_l">ľ,ĺ,ł</string>
+    <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK -->
+    <string name="more_keys_for_a">&#x00E1;,&#x00E4;,&#x0101;,&#x00E0;,&#x00E2;,&#x00E3;,&#x00E5;,&#x00E6;,&#x0105;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK -->
+    <string name="more_keys_for_e">&#x00E9;,&#x011B;,&#x0113;,&#x0117;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0119;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+0131: "ı" LATIN SMALL LETTER DOTLESS I -->
+    <string name="more_keys_for_i">&#x00ED;,&#x012B;,&#x012F;,&#x00EC;,&#x00EE;,&#x00EF;,&#x0131;</string>
+    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_o">&#x00F4;,&#x00F3;,&#x00F6;,&#x00F2;,&#x00F5;,&#x0153;,&#x0151;,&#x00F8;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+         U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE -->
+    <string name="more_keys_for_u">&#x00FA;,&#x016F;,&#x00FC;,&#x016B;,&#x0173;,&#x00F9;,&#x00FB;,&#x0171;</string>
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA -->
+    <string name="more_keys_for_s">&#x0161;,&#x00DF;,&#x015B;,&#x015F;</string>
+    <!-- U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+         U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+         U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
+    <string name="more_keys_for_n">&#x0148;,&#x0146;,&#x00F1;,&#x0144;,&#x0144;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x010D;,&#x00E7;,&#x0107;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
+    <string name="more_keys_for_y">&#x00FD;,&#x00FF;</string>
+    <!-- U+010F: "ď" LATIN SMALL LETTER D WITH CARON -->
+    <string name="more_keys_for_d">&#x010F;</string>
+    <!-- U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+         U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+         U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA -->
+    <string name="more_keys_for_r">&#x0155;,&#x0159;,&#x0157;</string>
+    <!-- U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+         U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA -->
+    <string name="more_keys_for_t">&#x0165;,&#x0163;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+         U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE -->
+    <string name="more_keys_for_z">&#x017E;,&#x017C;,&#x017A;</string>
+    <!-- U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA -->
+    <string name="more_keys_for_k">&#x0137;</string>
+    <!-- U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+         U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+         U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+         U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
+    <string name="more_keys_for_l">&#x013E;,&#x013A;,&#x013C;,&#x0142;</string>
+    <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+         U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
 </resources>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index b7ab8f2..95ebad1 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -21,12 +21,14 @@
 <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">"Klávesnica Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Klávesnica Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavenia klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávania textu a údajov"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Opravy pravopisu Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kontrola pravopisu Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kontrola pravopisu Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavenia kontroly pravopisu"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Použiť údaje o blízkosti"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Na kontr. pravopis. použiť algor. vzdialenosti ako pri kláves."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhľadať kontakty"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kontrola pravopisu používa záznamy z vášho zoznamu kontaktov"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Pri stlačení klávesu vibrovať"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk pri stlačení klávesu"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Zobraziť znaky pri stlačení klávesu"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Oprava textu"</string>
     <string name="misc_category" msgid="6894192814868233453">"Ďalšie možnosti"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Rozšírené nastavenia"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Možnosti pre skúsených používateľov"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Možnosti pre odborníkov"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Prepnúť na iné metódy vstupu"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Kláves na prepnutie jazyka pokrýva aj ďalšie metódy vstupu"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Blok. kláves prep. jazyka"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Onesk. zrušenia kľúč. kon. okna"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez oneskorenia"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predvolená"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vždy zobrazovať"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Zobraziť v režime na výšku"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vždy skrývať"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Zobraziť kláves Nastavenia"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Automatické opravy"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Stlačením medzerníka a interpunkcie sa aut. opravia chybné slová"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuté"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Uložené"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Hľadať"</string>
     <string name="label_next_key" msgid="362972844525672568">"Ďalej"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Pred."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Hotovo"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Odoslať"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nie je zadaný žiadny text"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kód klávesu %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Povolený kláves Shift"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Povolený kláves Caps Lock"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kláves Shift je zapnutý (zakážete ho klepnutím)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Kláves Caps Lock je zapnutý (zakážete ho klepnutím)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboly"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Písmená"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Hlasový vstup"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Usmiata tvár"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Čiarka"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Bodka"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Ľavá zátvorka"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Pravá zátvorka"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dvojbodka"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Bodkočiarka"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Výkričník"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Otáznik"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Úvodzovky"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Jednoduché úvodzovky"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Bodka"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Odmocnina"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pí"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Trademark"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Percento"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Hviezdička"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Libra"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Tri bodky"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Dolné úvodzovky"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Hlasový vstup"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Pre váš jazyk aktuálne nie je hlasový vstup podporovaný, ale funguje v angličtine."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Hlasový vstup používa rozpoznávanie hlasu Google. Na používanie hlasového vstupu sa vzťahujú "<a href="http://m.google.com/privacy">"Pravidlá ochrany osobných údajov pre mobilné služby"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Ak chcete vypnúť hlasový vstup, prejdite na nastavenia metódy vstupu."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Ak chcete použiť hlasový vstup, stlačte tlačidlo mikrofón."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Hovorte"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Prebieha spracovanie"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Chyba. Skúste to znova."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Pripojenie sa nepodarilo."</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Chyba, reč je príliš dlhá."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problém so zvukom"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Chyba servera"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Nebola zistená žiadna reč."</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Nenašli sa žiadne zhody"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Hlasové vyhľadávanie nie je nainštalované"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Tip:"</b>" Ak chcete aktivovať hlasový vstup, prejdite prstom po klávesnici."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Tip:"</b>" Nabudúce skúste vysloviť interpunkciu, napríklad „bodka“, „čiarka“ alebo „otáznik“."</string>
-    <string name="cancel" msgid="6830980399865683324">"Zrušiť"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Kláves Shift je povolený"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Kláves Caps Lock je povolený"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Kláves Shift je zakázaný"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Režim symbolov"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Režim písmen"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Režim telefónu"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Režim telefónnych symbolov"</string>
     <string name="voice_input" msgid="3583258583521397548">"Kľúč hlasového vstupu"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Na hlavnej klávesnici"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Na klávesnici so symbolmi"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofón na hlavnej klávesnici"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrofón na klávesnici so symbolmi"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hlasový vstup je zakázaný"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Výber metódy vstupu"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurovať metódy vstupu"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Jazyky vstupu"</string>
     <string name="select_language" msgid="3693815588777926848">"Jazyky vstupu"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Ďalším dotykom slovo uložíte"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"K dispozícii je slovník"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Povoliť spätnú väzbu od používateľov"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Automatickým zasielaním štatistík o využívaní editora metódy vstupu a správ o jeho zlyhaní do služby Google môžete prispieť k vylepšeniu tohto nástroja."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Motív klávesnice"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Nemecká klávesnica QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglická klávesnica (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglická klávesnica (US)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Režim štúdie použiteľnosti"</string>
diff --git a/java/res/values-sl/donottranslate-more-keys.xml b/java/res/values-sl/donottranslate-more-keys.xml
new file mode 100644
index 0000000..ccff2ac
--- /dev/null
+++ b/java/res/values-sl/donottranslate-more-keys.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x0161;</string>
+    <!-- U+010D: "č" LATIN SMALL LETTER C WITH CARON
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE -->
+    <string name="more_keys_for_c">&#x010D;,&#x0107;</string>
+    <!-- U+0111: "đ" LATIN SMALL LETTER D WITH STROKE -->
+    <string name="more_keys_for_d">&#x0111;</string>
+    <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON -->
+    <string name="more_keys_for_z">&#x017E;</string>
+</resources>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index d7f357a..6368948 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -21,12 +21,14 @@
 <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">"Tipkovnica Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tipkovnica Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavitve tipkovnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti vnosa"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Preverjanje črkovanja za Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Črkovalnik za Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Črkovalnik za Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavitve preverjanja črkovanja"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Uporabi podatke bližine"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Uporaba algoritma za preverjanje črkovanja na podlagi bližine znakov na tipkovnici"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Iskanje imen stikov"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Črkovalnik uporablja vnose s seznama stikov"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibriranje ob pritisku tipke"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvok ob pritisku tipke"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Povečaj črko ob pritisku"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Popravljanje besedila"</string>
     <string name="misc_category" msgid="6894192814868233453">"Druge možnosti"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Dodatne nastavitve"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Možnosti za izkušene uporabnike"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Možnosti za strokovnjake"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Prekl. na drug nač. vnosa"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Tipka za preklop jezika, ki vključuje tudi druge načine vnosa"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Onemogoči tipko za preklop jezika"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Trajanje povečanja tipke"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Brez zakasnitve"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Privzeto"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vedno pokaži"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Pokaži v pokončnem načinu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vedno skrij"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Tipka za prikaz nastavitev"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Samodejni popravek"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Preslednica in ločila samodejno popravijo napačno vtipkane besede"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izklopljeno"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: shranjeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pojdi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Naprej"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Nazaj"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Dokončano"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Pošlji"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ni vnesenega besedila"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Koda tipke %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Tipka »Shift« je omogočena"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Funkcija »Caps Lock« je omogočena"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift je vklopljen (dotaknite se, da onemogočite)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock je vklopljen (dotaknite se, da onemogočite)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simboli"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Pisma"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Glasovni vnos"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smeško"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Vračalka"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Vejica"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Pika"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Levi oklepaj"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Desni oklepaj"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dvopičje"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Podpičje"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Klicaj"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Vprašaj"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dvojni narekovaji"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Enojni narekovaj"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Pika"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Koren"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Blagovna znamka"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Odstotek"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Zvezdica"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Lojtra"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Tri pike"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Spodnji dvojni narekovaji"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Glasovni vnos"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Glasovni vnos trenutno ni podprt v vašem jeziku, deluje pa v angleščini."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Glasovni vnos uporablja Googlovo prepoznavanje govora. Zanj velja "<a href="http://m.google.com/privacy">"pravilnik o zasebnosti za mobilne naprave"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Glasovni vnos izklopite v nastavitvah načina vnosa."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Če želite uporabljati glasovni vnos, pritisnite gumb z mikrofonom."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Začnite govoriti"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Obdelava"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Napaka. Poskusite znova."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Povezava ni mogoča"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Napaka, preveč govora."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Težave z zvokom"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Napaka strežnika"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Govora se ne sliši"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Ni rezultatov"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Glasovno iskanje ni nameščeno"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Nasvet:"</b>" za govorjenje s prstom povlecite po tipkovnici"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Nasvet:"</b>" naslednjič poskusite ločila izgovoriti, npr. »pika«, »vejica« ali »vprašaj«."</string>
-    <string name="cancel" msgid="6830980399865683324">"Prekliči"</string>
-    <string name="ok" msgid="7898366843681727667">"V redu"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Način »Shift« je omogočen"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Način »Caps Lock« je omogočen"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Način »Shift« je onemogočen"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Način simbolov"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Način črk"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Način telefona"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Način simbolov telefona"</string>
     <string name="voice_input" msgid="3583258583521397548">"Tipka za glasovni vnos"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Na glavni tipkovnici"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Na tipk. s simboli"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mik. na glavni tipk."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mik. na tipk. s sim."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Glas. vnos je onem."</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Izberite način vnosa"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Nastavitev načinov vnosa"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Jeziki vnosa"</string>
     <string name="select_language" msgid="3693815588777926848">"Jeziki vnosa"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Še enkrat se dotaknite, da shranite"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dotaknite se še enkrat, da shranite"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Slovar je na voljo"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Omogoči povratne informacije uporabnikov"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"S samodejnim pošiljanjem statističnih podatkov o uporabi in poročil o zrušitvah Googlu nam lahko pomagate izboljšati urejevalnik načina vnosa."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema tipkovnice"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Nemška QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"angleščina (Združeno kraljestvo)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angleščina (ZDA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Način za preučevanje uporabnosti"</string>
diff --git a/java/res/values-sr/donottranslate-more-keys.xml b/java/res/values-sr/donottranslate-more-keys.xml
new file mode 100644
index 0000000..e85d3d7
--- /dev/null
+++ b/java/res/values-sr/donottranslate-more-keys.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE -->
+    <string name="keylabel_for_south_slavic_row1_6">&#x0437;</string>
+    <!-- U+045B: "ћ" CYRILLIC SMALL LETTER TSHE -->
+    <string name="keylabel_for_south_slavic_row2_11">&#x045B;</string>
+    <!-- U+0455: "ѕ" CYRILLIC SMALL LETTER DZE -->
+    <string name="keylabel_for_south_slavic_row3_1">&#x0455;</string>
+    <!-- U+0452: "ђ" CYRILLIC SMALL LETTER DJE -->
+    <string name="keylabel_for_south_slavic_row3_8">&#x0452;</string>
+    <!-- U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE -->
+    <string name="more_keys_for_cyrillic_ie">&#x0450;</string>
+    <!-- U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE -->
+    <string name="more_keys_for_cyrillic_i">&#x045D;</string>
+    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+    <!-- <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201E;,&#x201C;,&#x201D;,&#x201F;,&#x00AB;,&#x00BB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+</resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 0906fce..49aefd1 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android тастатура"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android тастатура (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Подешавања Android тастатуре"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опције уноса"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android исправљање"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android провера правописа"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android провера правописа (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Подешавања провере правописа"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Употреба података близине"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Употреба алгоритма близине попут тастатуре за проверу правописа"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Потражи имена контаката"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Контролор правописа користи уносе са листе контаката"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вибрирај на притисак тастера"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук на притисак тастера"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Искачући прозор приликом притиска тастера"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Исправљање текста"</string>
     <string name="misc_category" msgid="6894192814868233453">"Друге опције"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Напредна подешавања"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Опције за искусне кориснике"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Опције за стручњаке"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Пребаци на друге методе уноса"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Тастер за пребацивање језика обухвата и друге методе уноса"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Искључи тастер за језике"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Одложи одбац. иск. прозора тастера"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без одлагања"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Подразумевано"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Увек прикажи"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Прикажи у усправном режиму"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Увек сакриј"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Прикажи тастер за подешавања"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Аутоматско исправљање"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Размак и интерпункција аутоматски исправљају грешке у куцању"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Искључи"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Сачувано"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Иди"</string>
     <string name="label_next_key" msgid="362972844525672568">"Следеће"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Прет."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Пошаљи"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст није унет"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Кôд тастера %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Тастер Shift је омогућен"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock је омогућен"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift је укључен (додирните да бисте га онемогућили)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock је укључен (додирните да бисте га онемогућили)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Симболи"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Слова"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Гласовни унос"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Смајли"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Зарез"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Тачка"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Лева заграда"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Десна заграда"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Две тачке"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Тачка-зарез"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Знак узвика"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Знак питања"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Дупли наводник"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Полунаводник"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Тачка"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Квадратни корен"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Пи"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Делта"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Жиг"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"За"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Звездица"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Фунта"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Три тачке"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Отворени доњи наводници"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Гласовни унос"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Гласовни унос тренутно није подржан за ваш језик, али функционише на енглеском."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Гласовни унос користи Google-ову функцију за препознавање гласа. Примењује се "<a href="http://m.google.com/privacy">"политика приватности за мобилне уређаје"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Да бисте искључили гласовни унос, идите на подешавања за начин уноса."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Да бисте користили гласовни унос, притисните дугме за микрофон."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Говорите сада"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Обрада"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Грешка. Покушајте поново."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Повезивање није могуће"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Грешка, говорите предуго."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Проблем са звуком"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Грешка сервера"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Не чује се говор"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Нема подударања"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Гласовна претрага није инсталирана"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Савет:"</b>" Превуците прстом преко тастатуре за гласовни унос"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Савет:"</b>" Следећи пут покушајте да изговорите знакове интерпункције као што су „тачка“, „зарез“ или „знак питања“."</string>
-    <string name="cancel" msgid="6830980399865683324">"Откажи"</string>
-    <string name="ok" msgid="7898366843681727667">"Потврди"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift је омогућен"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock је омогућен"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift је онемогућен"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Режим симбола"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Режим слова"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Режим телефона"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Режим симбола телефона"</string>
     <string name="voice_input" msgid="3583258583521397548">"Тастер за гласовни унос"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"На главној тастатури"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"На тастатури са симболима"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Микрофон на главној тастатури"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Микрофон на тастатури са симболима"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Гласовни унос је онемогућен"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Изаберите метод уноса"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Конфигурисање метода уноса"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Језици за унос"</string>
     <string name="select_language" msgid="3693815588777926848">"Језици уноса"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Поново додирните да бисте сачували"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Речник је доступан"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Омогући повратну информацију корисника"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Помозите да се побољша овај уређивач режима уноса тако што ће се аутоматски послати статистика о коришћењу и извештаји о грешкама компанији Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема тастатуре"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY тастатура за немачки"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"енглески (УК)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"енглески (САД)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим за студију могућности коришћења"</string>
diff --git a/java/res/values-sv/donottranslate-more-keys.xml b/java/res/values-sv/donottranslate-more-keys.xml
index 1fa29a8..d479191 100644
--- a/java/res/values-sv/donottranslate-more-keys.xml
+++ b/java/res/values-sv/donottranslate-more-keys.xml
@@ -18,12 +18,37 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_e">3,é,è,ê,ë,ę</string>
-    <string name="more_keys_for_o">9,œ,ô,ò,ó,õ,ō</string>
-    <string name="more_keys_for_u">7,ü,û,ù,ú,ū</string>
-    <string name="more_keys_for_s">ß,ś,š</string>
-    <string name="keylabel_for_scandinavia_row2_10">ö</string>
-    <string name="keylabel_for_scandinavia_row2_11">ä</string>
-    <string name="more_keys_for_scandinavia_row2_10">ø</string>
-    <string name="more_keys_for_scandinavia_row2_11">æ</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK -->
+    <string name="more_keys_for_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0119;</string>
+    <!-- U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x0153;,&#x00F4;,&#x00F2;,&#x00F3;,&#x00F5;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FC;,&#x00FB;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE -->
+    <string name="keylabel_for_nordic_row1_11">&#x00E5;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_nordic_row2_11">&#x00E4;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_nordic_row2_10">&#x00F8;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="more_keys_for_nordic_row2_11">&#x00E6;</string>
 </resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 46760bb..3c5ecd5 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -21,12 +21,14 @@
 <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">"Androids tangentbord"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Androids tangentbord (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Inställningar för Androids tangentbord"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inmatningsalternativ"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-korrigering"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Stavningskontroll i Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Stavningskontroll i Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Inställningar för stavningskontroll"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Använd närhetsinformation"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Använd tangentbordsliknande närhetsalgoritm för stavningskontroll"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Sök namn på kontakter"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"I stavningskontrollen används poster från kontaktlistan"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrera vid tangenttryck"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Knappljud"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup vid knapptryck"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Textkorrigering"</string>
     <string name="misc_category" msgid="6894192814868233453">"Andra alternativ"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Avancerade inställningar"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Alternativ för expertanvändare"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Alternativ för experter"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Byt till annan inmatning"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Språkbytesknappen omfattar även andra inmatningsmetoder"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Stäng av språkbytesknapp"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Ta bort popup-fördröjning"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Fördröj inte"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Visa alltid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Visa stående"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Dölj alltid"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Visa inställningsknapp"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autokorrigering"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Blanksteg/skiljetecken rättar felstavning"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: sparat"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Kör"</string>
     <string name="label_next_key" msgid="362972844525672568">"Nästa"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Föreg"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Färdig"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Skicka"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ingen text har angetts"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Nyckelkod %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Skift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Skift aktiverat"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock är aktiverat"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Skift på (knacka lätt för att inaktivera)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock på (knacka lätt för att inaktivera)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboler"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Bokstäver"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Röstinmatning"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Uttryckssymbol"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Retur"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Komma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Punkt"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Vänster parentes"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Högerparentes"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Kolon"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikolon"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Utropstecken"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Frågetecken"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dubbla citattecken"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Enkla citattecken"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Punkt"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Kvadratrot"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Trademark"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Care of"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Stjärna"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Fyrkant"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellips"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Nedre dubbla citattecken"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Röstindata"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Röstindata stöds inte på ditt språk än, men tjänsten fungerar på engelska."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Röstinmatning använder sig av Googles tjänst för taligenkänning. "<a href="http://m.google.com/privacy">"Sekretesspolicyn för mobila enheter"</a>" gäller."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Om du vill stänga av röstinmatning öppnar du inställningarna för inmatningsmetod."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Om du vill använda röstinmatning trycker du på mikrofonknappen."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Tala nu"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Fungerar"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Fel. Försök igen."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Det gick inte att ansluta"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Fel, för mycket tal."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Ljudproblem"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Serverfel"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Hörde inget tal"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Inga träffar hittades"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Voice Search är inte installerat"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Tips!"</b>" Dra över tangentbordet om du vill tala"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Tips!"</b>" Nästa gång testar du att säga skiljetecknen, som \"punkt\", \"komma\" eller \"frågetecken\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Avbryt"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Skift är aktiverat"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock är aktiverat"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Skift är inaktiverat"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbolläge"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Bokstavsläge"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefonläge"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefonsymbolläge"</string>
     <string name="voice_input" msgid="3583258583521397548">"Röstinmatningsknapp"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"På huvudtangentbord"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"På symboltangentbord"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mick huvudtangentbord"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mick bland symboler"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Röstinmatning inaktiv"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Välj inmatningsmetod"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurera inmatningsmetoder"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Inmatningsspråk"</string>
     <string name="select_language" msgid="3693815588777926848">"Inmatningsspråk"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tryck igen för att spara"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Spara genom att trycka igen"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"En ordlista är tillgänglig"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktivera synpunkter från användare"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Du kan hjälpa till att förbättra inmatningsmetoden genom att automatiskt skicka användningsstatistik och felrapporter till Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tangentbordstema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Tyskt QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engelskt (brittiskt)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engelskt (amerikanskt)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Läge för studie av användbarhet"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 822907b..e849ade 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -21,12 +21,14 @@
 <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">"Kibodi ya Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Kicharazio cha Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Mipangilio ya kibodi ya Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Chaguo za uingizaji"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Masahihisho ya Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kikagua tahajia cha Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kikagua tahajia cha Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Mipangilio ya kukagua sarufi"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Tumia data ya ukaribu"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Tumia kibodi kama ukaribu wa algorithmu kwa ukaguzi wa sarufi"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Angalia majina ya wasiliani"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kikagua tahajia hutumia ingizo kutoka kwa orodha yako ya anwani"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Tetema unabofya kitufe"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Toa sauti unapobofya kitufe"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ibuka kitufe kinapobonyezwa"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Marekebisho ya maandishi"</string>
     <string name="misc_category" msgid="6894192814868233453">"Chaguo zingine"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Mipangilio mahiri"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Machaguo ya watumiaji wataalamu"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Chaguo za wataalamu"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Badilisha kwa mbinu zingine za ingizo"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Ufunguo wa kubadilisha lugha unashughulikia mbinu zingine za ingizo pia"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Zuia ufunguo wa kubadili lugha"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Kuchelewesha kutupa kitufe ibukizi"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Hakuna kuchelewa"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Chaguo-msingi"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Onyesha kila wakati"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Onyesha kwenye hali wima"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ficha kila wakati"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Onyesha kitufe cha mipangilio"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Usahihishaji Kioto"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Kiaamba na kiakifishi hurekebisha maneno ambayo yamechapishwa vibaya"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Zima"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Imehifadhiwa"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Nenda"</string>
     <string name="label_next_key" msgid="362972844525672568">"Ifuatayo"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Iliyotangulia"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Kwisha"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Tuma"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Hakuna maandishi yaliyoingizwa"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Msimbo wa kitufe %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Badilisha"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Kuhamisha kumewezeshwa"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Herufi kubwa imewezeshwa"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift imewashwa (gonga ili kulemaza)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock imewashwa (gonga ili kulemaza)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Futa"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Alama"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Herufi"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Uingizaji sauti"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Uso wenye tabasamu"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Rudi"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Koma"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Muda"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Mabano ya kushoto"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"mabano ya kulia"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Nukta mbili juu na chini"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Semikoloni"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Alama ya mshangao"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Alama ya kiulizio"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Nukuu mara mbili"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Nukuu moja"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Nukta"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Square root"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Chapa ya Biashara"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Kwa ulinzi wa"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Nyota"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Pauni"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Nukuu  ya chini maradufu"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Uingizaji wa sauti"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Uingizaji wa sauti hauhimiliwi kwa lugha yako kwa sasa, lakini inafanya kazi kwa Kiingereza."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Uingizaji wa sauti hutumia utambuaji wa usemi wa Google. "<a href="http://m.google.com/privacy">"Sera ya Faragha ya Simu za mkononi "</a>" hutumika."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Ili kuzima uingizaji sauti, nenda kwa mipangilio ya mbinu ya uingizaji."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Ili kutumia uingizaji wa sauti, bonyeza kitufe cha maikrofoni."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Ongea sasa"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Inafanya kazi"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Hitilafu. Tafadhali jaribu tena."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Haiwezi kuunganisha"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Hitilafu, usemi ni zaidi."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Tatizo la sauti"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Hitilafu ya Seva"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Hakuna matamshi yaliyosikizwa"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Hakuna zinazolingana zilizopatikana."</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Utafutaji wa sauti haujawekwa"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Kidokezo:"</b>" Telezesha kidole kwenye kibodi ili utamke"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Kidokezo:"</b>" Wakati mwingine, jaribu kutamka uakifishaji kama vile \"kituo\", \"koma\", au \"kiulizio cha swali\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Ghairi"</string>
-    <string name="ok" msgid="7898366843681727667">"Sawa"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift imewezeshwa"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock imewezeshwa"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift imelemazwa"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Hali ya alama"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Hali ya barua"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Hali ya simu"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Hali ya alama za simu"</string>
     <string name="voice_input" msgid="3583258583521397548">"Kibao cha kuingizia sauti"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Kwenye kibodi kuu"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Kwenye kibodi ya ishara"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Maikrofoni kwenye kibodi kuu"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Maikrofoni kwenye kibodi ya ishara"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Uingizaji sauti umelemazwa"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Chagua mtindo wa uingizaji"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Sanidi mbinu za uingizaji"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Lugha za uingizaji"</string>
     <string name="select_language" msgid="3693815588777926848">"Lugha zinazoruhusiwa"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Gusa tena ili kuhifadhi"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Gusa tena ili kuhifadhi"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamusi inapatikana"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Wezesha maoni ya watumiaji"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Saidia kuimarisha mbinu ya uingizaji wa kihariri, kwa kutuma takwimu za matumizi na ripoti za kuvurugika kwa Google kiotomatiki."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Maandhari ya kibodi"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY ya Kijerumani"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Kiingereza cha (Uingereza)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Kiingereza cha (Marekani)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modi ya uchunguzi wa utumizi"</string>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index c945ea1..8a59c9b 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">45.0mm</dimen>
+    <!-- Preferable keyboard height in absolute scale: 45.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">283.5dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
 
     <fraction name="keyboard_top_padding">2.444%p</fraction>
@@ -28,19 +29,19 @@
     <fraction name="key_bottom_gap">4.911%p</fraction>
     <fraction name="key_horizontal_gap">1.284%p</fraction>
 
-    <dimen name="keyboardHeight_stone">45.0mm</dimen>
     <fraction name="key_bottom_gap_stone">4.355%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.505%p</fraction>
 
     <fraction name="key_bottom_gap_gb">5.200%p</fraction>
     <fraction name="key_horizontal_gap_gb">1.447%p</fraction>
 
+    <fraction name="key_bottom_gap_ics">4.0%p</fraction>
     <fraction name="keyboard_bottom_padding_ics">0.0%p</fraction>
 
-    <dimen name="popup_key_height">13.0mm</dimen>
+    <dimen name="popup_key_height">81.9dp</dimen>
 
     <!-- left or right padding of label alignment -->
-    <dimen name="key_label_horizontal_padding">18dip</dimen>
+    <dimen name="key_label_horizontal_padding">18dp</dimen>
 
     <fraction name="key_letter_ratio">45%</fraction>
     <fraction name="key_large_letter_ratio">48%</fraction>
@@ -48,8 +49,9 @@
     <fraction name="key_hint_letter_ratio">23%</fraction>
     <fraction name="key_hint_label_ratio">34%</fraction>
     <fraction name="key_uppercase_letter_ratio">29%</fraction>
+    <fraction name="spacebar_text_ratio">33.33%</fraction>
 
-    <dimen name="suggestions_strip_padding">40.0mm</dimen>
+    <dimen name="suggestions_strip_padding">252.0dp</dimen>
     <integer name="max_more_suggestions_row">5</integer>
     <fraction name="min_more_suggestions_width">50%</fraction>
 </resources>
diff --git a/java/res/values-fr-rCA/donottranslate-more-keys.xml b/java/res/values-sw600dp-land/keyboard-heights.xml
similarity index 65%
copy from java/res/values-fr-rCA/donottranslate-more-keys.xml
copy to java/res/values-sw600dp-land/keyboard-heights.xml
index 80e9d93..93f9824 100644
--- a/java/res/values-fr-rCA/donottranslate-more-keys.xml
+++ b/java/res/values-sw600dp-land/keyboard-heights.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,9 +17,12 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z"></string>
+
+<!-- Preferable keyboard height in absolute scale: 45.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,265.4378</item>
+    </string-array>
 </resources>
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 1854a86..c507bd2 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -19,25 +19,25 @@
 -->
 
 <resources>
-    <bool name="config_enable_show_settings_key_option">true</bool>
-    <bool name="config_default_show_settings_key">false</bool>
     <bool name="config_enable_show_voice_key_option">false</bool>
     <bool name="config_enable_show_popup_on_keypress_option">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
-    <bool name="config_sliding_key_input_enabled">false</bool>
-    <bool name="config_digit_more_keys_enabled">false</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
     <bool name="config_default_popup_preview">false</bool>
     <bool name="config_default_sound_enabled">true</bool>
     <bool name="config_auto_correction_spacebar_led_enabled">false</bool>
-    <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
-    <bool name="config_show_mini_keyboard_at_touched_point">true</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
     <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
     <!-- Long pressing space will invoke IME switcher if > 0, never invoke IME switcher if == 0 -->
     <integer name="config_long_press_space_key_timeout">0</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_id" translatable="false">5</string>
-    <string name="config_text_size_of_language_on_spacebar" translatable="false">medium</string>
+    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
     <integer name="config_max_more_keys_column">5</integer>
+    <!--
+        Configuration for LatinKeyboardView
+    -->
+    <bool name="config_sliding_key_input_enabled">false</bool>
+    <!-- Showing more keys keyboard, just above the touched point if true, aligned to the key if
+         false -->
+    <bool name="config_show_more_keys_keyboard_at_touched_point">true</bool>
 </resources>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index f33e9f2..cb2a861 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -19,36 +19,37 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">48.0mm</dimen>
-    <fraction name="maxKeyboardHeight">50%p</fraction>
+    <!-- Preferable keyboard height in absolute scale: 48.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">302.4dp</dimen>
+    <fraction name="maxKeyboardHeight">46%p</fraction>
     <fraction name="minKeyboardHeight">-35.0%p</fraction>
 
-    <dimen name="popup_key_height">10.0mm</dimen>
+    <dimen name="popup_key_height">63.0dp</dimen>
 
     <fraction name="keyboard_top_padding">2.291%p</fraction>
     <fraction name="keyboard_bottom_padding">0.0%p</fraction>
     <fraction name="key_bottom_gap">3.750%p</fraction>
     <fraction name="key_horizontal_gap">1.857%p</fraction>
 
-    <dimen name="keyboardHeight_stone">48.0mm</dimen>
     <fraction name="key_bottom_gap_stone">3.75%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.602%p</fraction>
 
     <fraction name="key_bottom_gap_gb">4.625%p</fraction>
     <fraction name="key_horizontal_gap_gb">2.113%p</fraction>
 
+    <fraction name="key_bottom_gap_ics">4.0%p</fraction>
     <fraction name="keyboard_bottom_padding_ics">0.0%p</fraction>
 
-    <dimen name="mini_keyboard_key_horizontal_padding">6dip</dimen>
+    <dimen name="more_keys_keyboard_key_horizontal_padding">6dp</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">15.6mm</dimen>
+    <dimen name="more_keys_keyboard_slide_allowance">98.3dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-13.0mm</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-81.9dp</dimen>
 
     <!-- left or right padding of label alignment -->
-    <dimen name="key_label_horizontal_padding">6dip</dimen>
+    <dimen name="key_label_horizontal_padding">6dp</dimen>
     <dimen name="key_hint_letter_padding">3dp</dimen>
     <dimen name="key_uppercase_letter_padding">3dp</dimen>
 
@@ -59,19 +60,21 @@
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">26%</fraction>
     <fraction name="key_preview_text_ratio">50%</fraction>
-    <dimen name="key_preview_height">15.0mm</dimen>
-    <dimen name="key_preview_offset">0.1in</dimen>
+    <fraction name="spacebar_text_ratio">32.14%</fraction>
+    <dimen name="key_preview_height">94.5dp</dimen>
+    <dimen name="key_preview_offset">16.0dp</dimen>
 
-    <dimen name="key_preview_height_ics">15.0mm</dimen>
-    <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <dimen name="key_preview_offset_ics">8.0dp</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-31.5dp</dimen>
 
-    <dimen name="suggestions_strip_height">44dip</dimen>
-    <dimen name="more_suggestions_row_height">44dip</dimen>
+    <dimen name="suggestions_strip_height">44dp</dimen>
+    <dimen name="more_suggestions_row_height">44dp</dimen>
     <integer name="max_more_suggestions_row">6</integer>
     <fraction name="min_more_suggestions_width">90%</fraction>
-    <dimen name="suggestions_strip_padding">15.0mm</dimen>
-    <dimen name="suggestion_min_width">0.3in</dimen>
-    <dimen name="suggestion_padding">12dip</dimen>
-    <dimen name="suggestion_text_size">22dip</dimen>
-    <dimen name="more_suggestions_hint_text_size">33dip</dimen>
+    <dimen name="suggestions_strip_padding">94.5dp</dimen>
+    <dimen name="suggestion_min_width">48.0dp</dimen>
+    <dimen name="suggestion_padding">12dp</dimen>
+    <dimen name="suggestion_text_size">22dp</dimen>
+    <dimen name="more_suggestions_hint_text_size">33dp</dimen>
 </resources>
diff --git a/java/res/values-fr-rCA/donottranslate-more-keys.xml b/java/res/values-sw600dp/keyboard-heights.xml
similarity index 65%
copy from java/res/values-fr-rCA/donottranslate-more-keys.xml
copy to java/res/values-sw600dp/keyboard-heights.xml
index 80e9d93..77e52be 100644
--- a/java/res/values-fr-rCA/donottranslate-more-keys.xml
+++ b/java/res/values-sw600dp/keyboard-heights.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,9 +17,12 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z"></string>
+
+<!-- Preferable keyboard height in absolute scale: 48.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,283.1337</item>
+    </string-array>
 </resources>
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index 664e8c1..b95c858 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=14.5mm -->
-    <dimen name="keyboardHeight">58.0mm</dimen>
+    <!-- Preferable keyboard height in absolute scale: 58.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">365.4dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
 
     <fraction name="keyboard_top_padding">1.896%p</fraction>
@@ -29,7 +30,6 @@
     <fraction name="key_bottom_gap">4.103%p</fraction>
     <fraction name="key_horizontal_gap">1.034%p</fraction>
 
-    <dimen name="keyboardHeight_stone">58.0mm</dimen>
     <fraction name="key_bottom_gap_stone">3.379%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.062%p</fraction>
 
@@ -41,10 +41,10 @@
     <fraction name="key_bottom_gap_ics">3.690%p</fraction>
     <fraction name="key_horizontal_gap_ics">1.030%p</fraction>
 
-    <dimen name="popup_key_height">13.0mm</dimen>
+    <dimen name="popup_key_height">81.9dp</dimen>
 
     <!-- left or right padding of label alignment -->
-    <dimen name="key_label_horizontal_padding">18dip</dimen>
+    <dimen name="key_label_horizontal_padding">18dp</dimen>
 
     <fraction name="key_letter_ratio">43%</fraction>
     <fraction name="key_large_letter_ratio">42%</fraction>
@@ -52,11 +52,11 @@
     <fraction name="key_hint_letter_ratio">23%</fraction>
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">24%</fraction>
-    <dimen name="key_preview_height">17.0mm</dimen>
+    <fraction name="spacebar_text_ratio">24.00%</fraction>
+    <dimen name="key_preview_height">107.1dp</dimen>
 
-    <dimen name="key_preview_height_ics">26.5mm</dimen>
-    <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <dimen name="key_preview_offset_ics">8.0dp</dimen>
 
-    <dimen name="suggestions_strip_padding">40.0mm</dimen>
+    <dimen name="suggestions_strip_padding">252.0dp</dimen>
     <fraction name="min_more_suggestions_width">50%</fraction>
 </resources>
diff --git a/java/res/values-fr-rCA/donottranslate-more-keys.xml b/java/res/values-sw768dp-land/keyboard-heights.xml
similarity index 65%
rename from java/res/values-fr-rCA/donottranslate-more-keys.xml
rename to java/res/values-sw768dp-land/keyboard-heights.xml
index 80e9d93..692c5a0 100644
--- a/java/res/values-fr-rCA/donottranslate-more-keys.xml
+++ b/java/res/values-sw768dp-land/keyboard-heights.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,9 +17,12 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z"></string>
+
+<!-- Preferable keyboard height in absolute scale: 58.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,342.1198</item>
+    </string-array>
 </resources>
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index c25139a..b78a6c6 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -19,25 +19,25 @@
 -->
 
 <resources>
-    <bool name="config_enable_show_settings_key_option">false</bool>
-    <bool name="config_default_show_settings_key">true</bool>
     <bool name="config_enable_show_voice_key_option">false</bool>
     <bool name="config_enable_show_popup_on_keypress_option">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
-    <bool name="config_sliding_key_input_enabled">false</bool>
-    <bool name="config_digit_more_keys_enabled">false</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
     <bool name="config_default_popup_preview">false</bool>
     <bool name="config_default_sound_enabled">true</bool>
     <bool name="config_auto_correction_spacebar_led_enabled">false</bool>
-    <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
-    <bool name="config_show_mini_keyboard_at_touched_point">true</bool>
     <!-- Long pressing space will invoke IME switcher if > 0, never invoke IME switcher if == 0 -->
     <integer name="config_long_press_space_key_timeout">0</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_id" translatable="false">5</string>
-    <string name="config_text_size_of_language_on_spacebar" translatable="false">medium</string>
+    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
     <integer name="config_max_more_keys_column">5</integer>
+    <!--
+        Configuration for LatinKeyboardView
+    -->
+    <bool name="config_sliding_key_input_enabled">false</bool>
+    <!-- Showing more keys keyboard, just above the touched point if true, aligned to the key if
+         false -->
+    <bool name="config_show_more_keys_keyboard_at_touched_point">true</bool>
     <!--  Screen metrics for logging.
             0 = "mdpi phone screen"
             1 = "hdpi phone screen"
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index 0d302c6..01e2284 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -19,9 +19,10 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=12mm -->
-    <dimen name="keyboardHeight">48.0mm</dimen>
-    <fraction name="maxKeyboardHeight">50%p</fraction>
+    <!-- Preferable keyboard height in absolute scale: 48.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">302.4dp</dimen>
+    <fraction name="maxKeyboardHeight">46%p</fraction>
     <fraction name="minKeyboardHeight">-35.0%p</fraction>
 
     <fraction name="keyboard_top_padding">2.291%p</fraction>
@@ -30,7 +31,6 @@
     <fraction name="key_bottom_gap">4.270%p</fraction>
     <fraction name="key_horizontal_gap">1.551%p</fraction>
 
-    <dimen name="keyboardHeight_stone">48.0mm</dimen>
     <fraction name="key_bottom_gap_stone">3.75%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.059%p</fraction>
 
@@ -41,17 +41,17 @@
     <fraction name="key_bottom_gap_ics">3.312%p</fraction>
     <fraction name="key_horizontal_gap_ics">1.066%p</fraction>
 
-    <dimen name="popup_key_height">10.0mm</dimen>
+    <dimen name="popup_key_height">63.0dp</dimen>
 
-    <dimen name="mini_keyboard_key_horizontal_padding">12dip</dimen>
+    <dimen name="more_keys_keyboard_key_horizontal_padding">12dp</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">15.6mm</dimen>
+    <dimen name="more_keys_keyboard_slide_allowance">98.3dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-13.0mm</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-81.9dp</dimen>
 
     <!-- left or right padding of label alignment -->
-    <dimen name="key_label_horizontal_padding">6dip</dimen>
+    <dimen name="key_label_horizontal_padding">6dp</dimen>
     <dimen name="key_hint_letter_padding">3dp</dimen>
     <dimen name="key_uppercase_letter_padding">3dp</dimen>
 
@@ -62,19 +62,21 @@
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">26%</fraction>
     <fraction name="key_preview_text_ratio">50%</fraction>
-    <dimen name="key_preview_height">15.0mm</dimen>
-    <dimen name="key_preview_offset">0.1in</dimen>
+    <fraction name="spacebar_text_ratio">29.03%</fraction>
+    <dimen name="key_preview_height">94.5dp</dimen>
+    <dimen name="key_preview_offset">16.0dp</dimen>
 
-    <dimen name="key_preview_height_ics">15.0mm</dimen>
-    <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <dimen name="key_preview_offset_ics">8.0dp</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-31.5dp</dimen>
 
-    <dimen name="suggestions_strip_height">44dip</dimen>
-    <dimen name="more_suggestions_row_height">44dip</dimen>
+    <dimen name="suggestions_strip_height">44dp</dimen>
+    <dimen name="more_suggestions_row_height">44dp</dimen>
     <integer name="max_more_suggestions_row">6</integer>
     <fraction name="min_more_suggestions_width">90%</fraction>
-    <dimen name="suggestions_strip_padding">15.0mm</dimen>
-    <dimen name="suggestion_min_width">46dip</dimen>
-    <dimen name="suggestion_padding">8dip</dimen>
-    <dimen name="suggestion_text_size">22dip</dimen>
-    <dimen name="more_suggestions_hint_text_size">33dip</dimen>
+    <dimen name="suggestions_strip_padding">94.5dp</dimen>
+    <dimen name="suggestion_min_width">46dp</dimen>
+    <dimen name="suggestion_padding">8dp</dimen>
+    <dimen name="suggestion_text_size">22dp</dimen>
+    <dimen name="more_suggestions_hint_text_size">33dp</dimen>
 </resources>
diff --git a/java/res/values-fr-rCA/donottranslate-more-keys.xml b/java/res/values-sw768dp/keyboard-heights.xml
similarity index 65%
copy from java/res/values-fr-rCA/donottranslate-more-keys.xml
copy to java/res/values-sw768dp/keyboard-heights.xml
index 80e9d93..77e52be 100644
--- a/java/res/values-fr-rCA/donottranslate-more-keys.xml
+++ b/java/res/values-sw768dp/keyboard-heights.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,9 +17,12 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
-    <string name="more_keys_for_z"></string>
+
+<!-- Preferable keyboard height in absolute scale: 48.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,283.1337</item>
+    </string-array>
 </resources>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 53f5660..15157c2 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -21,12 +21,14 @@
 <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">"แป้นพิมพ์ Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"การตั้งค่าแป้นพิมพ์ Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"การแก้ไขของ Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"แอนดรอยด์ตรวจสอบการสะกด"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"แอนดรอยด์ตรวจสอบการสะกด (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"การตั้งค่าการตรวจสอบการสะกด"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"ใช้ข้อมูลที่ใกล้เคียง"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"ใช้อัลกอริทึมใกล้เคียงที่คล้ายกับแป้นพิมพ์สำหรับตรวจสอบการสะกด"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ค้นหารายชื่อติดต่อ"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"เครื่องมือตรวจการสะกดใช้รายการจากรายชื่อติดต่อของคุณ"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"สั่นเมื่อกดปุ่ม"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"ส่งเสียงเมื่อกดปุ่ม"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"ป๊อปอัปเมื่อกดแป้น"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"การแก้ไขข้อความ"</string>
     <string name="misc_category" msgid="6894192814868233453">"ตัวเลือกอื่นๆ"</string>
     <string name="advanced_settings" msgid="362895144495591463">"การตั้งค่าขั้นสูง"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"ตัวเลือกสำหรับผู้ใช้ที่มีความเชี่ยวชาญ"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"ตัวเลือกสำหรับผู้เชี่ยวชาญ"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"ใช้วิธีการป้อนข้อมูลอื่น"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"แป้นสลับภาษาครอบคลุมวิธีการป้อนข้อมูลอื่นๆ ด้วย"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"ยกเลิกแป้นสลับภาษา"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"หน่วงเวลาก่อนปิดป๊อปอัพหลัก"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ไม่มีการหน่วงเวลา"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ค่าเริ่มต้น"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"แสดงทุกครั้ง"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"แสดงในโหมดแนวตั้ง"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ซ่อนทุกครั้ง"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"แสดงแป้นการตั้งค่า"</string>
     <string name="auto_correction" msgid="4979925752001319458">"การแก้ไขอัตโนมัติ"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"กดเว้นวรรคและเครื่องหมายจะแก้คำผิดอัตโนมัติ"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ปิด"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : บันทึกแล้ว"</string>
     <string name="label_go_key" msgid="1635148082137219148">"ไป"</string>
     <string name="label_next_key" msgid="362972844525672568">"ถัดไป"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"ก่อนหน้า"</string>
     <string name="label_done_key" msgid="2441578748772529288">"เสร็จสิ้น"</string>
     <string name="label_send_key" msgid="2815056534433717444">"ส่ง"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"ไม่มีข้อความ"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"รหัสคีย์ %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"เปิดใช้งาน Shift แล้ว"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"เปิดใช้งาน Caps Lock แล้ว"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift เปิดอยู่ (แตะเพื่อปิดใช้งาน)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock เปิดอยู่ (แตะเพื่อปิดใช้งาน)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"ลบ"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"สัญลักษณ์"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"ตัวอักษร"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"การป้อนข้อมูลด้วยเสียง"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"หน้ายิ้ม"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"เครื่องหมายจุลภาค"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"มหัพภาค"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"วงเล็บซ้าย"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"วงเล็บขวา"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"เครื่องหมายจุดคู่"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"อัฒภาค"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"อัศเจรีย์"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"เครื่องหมายคำถาม"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"อัญประกาศ"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"เครื่องหมายคำพูดเดี่ยว"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"เครื่องหมายจุด"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"รากที่สอง"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"เดลตา"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"เครื่องหมายการค้า"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Care of"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"ติดดาว"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"ปอนด์"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"จุดไข่ปลา"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"อัญประกาศล่าง"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"การป้อนข้อมูลด้วยเสียง"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ขณะนี้การป้อนข้อมูลด้วยเสียงยังไม่ได้รับการสนับสนุนในภาษาของคุณ แต่ใช้ได้ในภาษาอังกฤษ"</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ป้อนข้อมูลด้วยเสียงใช้การจดจำคำพูดของ Google "<a href="http://m.google.com/privacy">" นโยบายส่วนบุคคลของมือถือ"</a>"มีผลบังคับใช้"</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"หากต้องการปิดการป้อนข้อมูลด้วยเสียง ไปที่การตั้งค่าวิธีการป้อนข้อมูล"</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"หากต้องการใช้การป้อนข้อมูลด้วยเสียง ให้กดปุ่มไมโครโฟน"</string>
-    <string name="voice_listening" msgid="467518160751321844">"พูดได้เลย"</string>
-    <string name="voice_working" msgid="6666937792815731889">"กำลังทำงาน"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"ข้อผิดพลาด โปรดลองอีกครั้ง"</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"ไม่สามารถเชื่อมต่อได้"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"ข้อผิดพลาด คำพูดยาวเกินไป"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"ปัญหาด้านเสียง"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"ข้อผิดพลาดของเซิร์ฟเวอร์"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"ไม่ได้ยินเสียง"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"ไม่พบรายการที่ตรงกัน"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"ไม่ได้ติดตั้ง Voice Search"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"คำแนะนำ:"</b>" กวาดผ่านแป้นพิมพ์เพื่อพูด"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"คำแนะนำ:"</b>" ครั้งต่อไป ให้ลองเอ่ยถึงเครื่องหมายวรรคตอน เช่น \"มหัพภาค\" \"จุลภาค\" หรือ \"เครื่องหมายคำถาม\""</string>
-    <string name="cancel" msgid="6830980399865683324">"ยกเลิก"</string>
-    <string name="ok" msgid="7898366843681727667">"ตกลง"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"เปิดใช้งาน Shift แล้ว"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"เปิดใช้งาน Caps Lock แล้ว"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"ปิดใช้งาน Shift แล้ว"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"โหมดสัญลักษณ์"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"โหมดตัวอักษร"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"โหมดโทรศัพท์"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"โหมดสัญลักษณ์โทรศัพท์"</string>
     <string name="voice_input" msgid="3583258583521397548">"แป้นการป้อนข้อมูลด้วยเสียง"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"บนแป้นพิมพ์หลัก"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"บนแป้นพิมพ์สัญลักษณ์"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"ไมค์บนแป้นพิมพ์หลัก"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"ไมค์บนแป้นพิมพ์สัญลักษณ์"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ปิดใช้งานป้อนข้อมูลด้วยเสียง"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"เลือกวิธีการป้อนข้อมูล"</string>
     <string name="configure_input_method" msgid="373356270290742459">"กำหนดค่าวิธีการป้อนข้อมูล"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"ภาษาในการป้อนข้อมูล"</string>
     <string name="select_language" msgid="3693815588777926848">"ภาษาสำหรับการป้อนข้อมูล"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← แตะอีกครั้งเพื่อบันทึก"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"มีพจนานุกรมให้ใช้งาน"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"เปิดใช้งานการแสดงความคิดเห็นจากผู้ใช้"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"ช่วยปรับปรุงตัวแก้ไขวิธีการป้อนข้อมูลนี้โดยการส่งสถิติการใช้งานและรายงานการขัดข้องถึง Google โดยอัตโนมัติ"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"ชุดรูปแบบแป้นพิมพ์"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY ภาษาเยอรมัน"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"อังกฤษ (สหราชอาณาจักร)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"อังกฤษ (อเมริกัน)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"โหมดศึกษาประโยชน์ในการใช้งาน"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 701963f..36f9002 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android keyboard"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Mga setting ng Android keyboard"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Mga pagpipilian sa input"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Pagwawasto sa Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Pang-check ng pagbabaybay ng Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Pang-check ng pagbabaybay ng Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Mga setting ng pang-check ng pagbabaybay"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Gamitin ang proximity data"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Gumamit ng proximity algorithm na tulad ng keyboard para sa pag-check ng pagbabaybay"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Maghanap pangalan contact"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Gumagamit pang-check pagbabaybay entry sa iyong listahan contact"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Mag-vibrate sa keypress"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tunog sa keypress"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup sa keypress"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Pagwawasto ng teksto"</string>
     <string name="misc_category" msgid="6894192814868233453">"Iba pang mga pagpipilian"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Mga advanced na setting"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Mga pagpipilian para sa mga ekspertong user"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Mga pagpipilian para sa mga dalubhasa"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Lipat iba paraan ng input"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Saklaw din ng key ng pagpalit ng wika ang ibang paraan ng input"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Pigilan key pagpalit wika"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Balewala antala key popup"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Walang antala"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Palaging ipakita"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Ipakita sa portrait mode"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Palaging itago"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Ipakita ang key ng mga setting"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Awtomatikong pagwasto"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Awto tinatama ng spacebar at bantas ang maling na-type"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Naka-off"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Na-save"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Punta"</string>
     <string name="label_next_key" msgid="362972844525672568">"Susunod"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Nkrn"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Tapos na"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Ipadala"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Walang tekstong inilagay"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Code ng key %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Pinagana ang shift"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Pinagana ang caps lock"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Naka-on ang shift (i-tap upang huwag paganahin)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Naka-on ang caps lock (i-tap upang huwag paganahin)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Tanggalin"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Mga Simbolo"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Mga Titik"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Input ng boses"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley na mukha"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Bumalik"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Kuwit"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Tuldok"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Kaliwang panaklong"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Kanang panaklong"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Tutuldok"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Tuldukuwit"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Tandang padamdam"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Tandang pananong"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Panipi"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Kudlit"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Tuldok"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Square root"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Trademark"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Care of"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Star"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Pound"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Mababang panipi"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Pag-input ng boses"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Hindi kasalukuyang suportado ang pag-input ng boses para sa iyong wika, ngunit gumagana sa Ingles."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Gumagamit ang pag-input ng boses ng speech recognition ng Google. Nalalapat "<a href="http://m.google.com/privacy">"Ang Patakaran sa Privacy ng Mobile"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Upang i-off ang pag-input ng boses, pumunta sa mga setting ng pamamaraan ng pag-input."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Upang gamitin ang pag-input ng boses, pindutin ang pindutan na mikropono."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Magsalita ngayon"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Nagtatrabaho"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Error. Pakisubukang muli."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Hindi makakonekta"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Error, masyadong maraming pananalita."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Problema sa audio"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Error sa server"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Walang narinig na pananalita"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Walang nakitang mga tugma"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Hindi naka-install ang paghahanap ng boses"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Pahiwatig:"</b>" Mag-swipe sa keyboard upang magsalita"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Pahiwatig:"</b>" Sa susunod, subukang magsalita ng bantas tulad ng \"tuldok\", \"kuwit\", o \"tandang pananong\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Kanselahin"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Pinagana ang shift"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Pinagana ang caps lock"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Hindi pinagana ang shift"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mode ng mga simbolo"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mode ng mga titik"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mode ng telepono"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mode ng mga simbolo ng telepono"</string>
     <string name="voice_input" msgid="3583258583521397548">"Voice input key"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Sa pangunahing keyboard"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Sa keyboard ng mga simbolo"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic sa pangunahing keyboard"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic sa keyboard ng mga simbolo"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hindi pinagana ang voice input"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Pumili ng paraan ng pag-input"</string>
     <string name="configure_input_method" msgid="373356270290742459">"I-configure ang mga pamamaraan ng pag-input"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Mag-input ng mga wika"</string>
     <string name="select_language" msgid="3693815588777926848">"Mga wika ng input"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Pinduting muli upang i-save"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Available ang diksyunaryo"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Paganahin ang feedback ng user"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Tumulong na pahusayin ang editor ng paraan ng pag-input na ito sa pamamagitan ng awtomatikong pagpapadala ng mga istatistika ng paggamit at mga ulat ng crash sa Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema ng keyboard"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"German na QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Ingles (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Ingles (Estados Unidos)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Study mode ng pagiging kapaki-pakinabang"</string>
diff --git a/java/res/values-tr/donottranslate-more-keys.xml b/java/res/values-tr/donottranslate-more-keys.xml
index 6906b35..1161811 100644
--- a/java/res/values-tr/donottranslate-more-keys.xml
+++ b/java/res/values-tr/donottranslate-more-keys.xml
@@ -18,11 +18,40 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">â</string>
-    <string name="more_keys_for_i">8,ı,î,ï,ì,í,į,ī</string>
-    <string name="more_keys_for_o">9,ö,ô,œ,ò,ó,õ,ø,ō</string>
-    <string name="more_keys_for_u">7,ü,û,ù,ú,ū</string>
-    <string name="more_keys_for_s">ş,ß,ś,š</string>
-    <string name="more_keys_for_g">ğ</string>
-    <string name="more_keys_for_c">ç,ć,č</string>
+    <!-- U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+    <string name="more_keys_for_a">&#x00E2;</string>
+    <!-- U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x0131;,&#x00EE;,&#x00EF;,&#x00EC;,&#x00ED;,&#x012F;,&#x012B;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F6;,&#x00F4;,&#x0153;,&#x00F2;,&#x00F3;,&#x00F5;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FC;,&#x00FB;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x015F;,&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x011F;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
 </resources>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 4ae7d78..1978a6f 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android klavyesi"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android klavye (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android klavye ayarları"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Giriş seçenekleri"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android düzeltme"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android yazım denetleyici"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android yazım denetleyici (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Yazım denetimi ayarları"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Yakınlık verilri kullan"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Yazım denetimi içn klavye benzeri yakınlık algoritması kullan"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kişi adlarını denetle"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Yazım denetleyici, kişi listenizdeki girişleri kullanır"</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>
     <string name="popup_on_keypress" msgid="123894815723512944">"Tuşa basıldığında pop-up aç"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Metin düzeltme"</string>
     <string name="misc_category" msgid="6894192814868233453">"Diğer seçenekler"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Gelişmiş ayarlar"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Uzman kullanıcılar için seçenekler"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Uzmanlar için seçenekler"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Diğ. giriş yöntem. geç"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Dil geçiş tuşu diğer giriş yöntemlerini de kapsar"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Dil geçiş tuşunu gösterme"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Tuş popup içn kaptm ertlm"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Gecikme yok"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Varsayılan"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Her zaman göster"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Dikey modda göster"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Her zaman gizle"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Ayarları göster tuşu"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Otomatik düzeltme"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Boşluk çbğ ve nokt işr yanlış yazılan kelimeleri oto düzeltir"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Kapalı"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Kaydedildi"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Git"</string>
     <string name="label_next_key" msgid="362972844525672568">"İleri"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Önceki"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Bitti"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Gönder"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Hiç metin girilmedi"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tuş kodu: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Üst Karakter"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Üst Karakter Etkin"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Büyük harf etkin"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Üst karakter açık (devre dışı bırakmak için hafifçe vurun)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Büyük harf kilidi açık (devre dışı bırakmak içinn hafifçe vurun)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simgeler"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Harfler"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Ses girişi"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Gülen yüz"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Virgül"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Nokta"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Sol parantez"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Sağ parantez"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"İki Nokta"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Noktalı virgül"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Ünlem işareti"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Soru işareti"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Çift tırnak"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Tek tırnak işareti"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Nokta"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Karekök"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Ticari marka"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Yüzde işareti"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Yıldız"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Kare"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Üç nokta"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Alt çift tırnak"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Ses girişi"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Ses girişi, şu anda sizin diliniz için desteklenmiyor ama İngilizce dilinde kullanılabilir."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Ses girişi Google\'ın konuşma tanıma işlevini kullanır. "<a href="http://m.google.com/privacy">" Mobil Gizlilik Politikası"</a>" geçerlidir."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Ses girişini kapatmak için giriş yöntemi ayarlarına gidin."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Ses girişini kullanmak için mikrofon düğmesine basın."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Şimdi konuşun"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Çalışıyor"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Hata. Lütfen tekrar deneyin."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Bağlanamadı"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Hata, çok uzun konuşma."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Ses sorunu"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Sunucu hatası"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Konuşma duyulmadı"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Eşleşme bulunamadı"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Sesle arama yüklenmedi"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"İpucu:"</b>" Konuşmak için parmağınızı klavye üzerinde kaydırın"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"İpucu:"</b>" Sonraki sefer, \"nokta\", \"virgül\" veya \"soru işareti\" gibi noktalama işaretlerini telaffuz etmeyi deneyin."</string>
-    <string name="cancel" msgid="6830980399865683324">"İptal"</string>
-    <string name="ok" msgid="7898366843681727667">"Tamam"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Üst karakter etkin"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Büyük harf kilidi etkin"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Üst karakter devre dışı"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Sembol modu"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Harf modu"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefon modu"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefon sembolleri modu"</string>
     <string name="voice_input" msgid="3583258583521397548">"Ses girişi tuşu"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Ana klavyede"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Simge klavyesinde"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Ana klavyede mikrfn"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Simge klavysnd mikrf"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Sesle grş devre dışı"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Giriş yöntemini seç"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Giriş yöntemlerini yapılandır"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Giriş dilleri"</string>
     <string name="select_language" msgid="3693815588777926848">"Giriş dilleri"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Kaydetmek için tekrar dokunun"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Sözlük kullanılabilir"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Kullanıcı geri bildirimini etkinleştir"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Kullanım istatistiklerini ve kilitlenme raporlarını Google\'a otomatik olarak göndererek bu giriş yöntemi düzenleyicisinin iyileştirilmesine yardımcı olun."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Klavye teması"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Almanca QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"İngilizce (BK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"İngilizce (ABD)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Kullanılabilirlik çalışması modu"</string>
diff --git a/java/res/values-uk/donottranslate-more-keys.xml b/java/res/values-uk/donottranslate-more-keys.xml
new file mode 100644
index 0000000..3239704
--- /dev/null
+++ b/java/res/values-uk/donottranslate-more-keys.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
+    <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+    <string name="keylabel_for_east_slavic_row2_1">&#x0456;</string>
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <string name="keylabel_for_east_slavic_row3_5">&#x0438;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_ha">&#x044A;</string>
+    <!-- U+0457: "ї" CYRILLIC SMALL LETTER YI -->
+    <string name="more_keys_for_east_slavic_row2_1">&#x0457;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
+</resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 234a9c5..6541c94 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -21,12 +21,14 @@
 <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">"Клавіатура Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавіатура Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Налашт-ня клавіат. Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Парам. введення"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Виправлення Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Засіб перевірки орфографії Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Засіб перевірки орфографії Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Налаштування перевірки орфографії"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Використ. дані близькості"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Для перевірки орфогр. викор. алгоритм близьк., аналог. клавіат."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукати імена контактів"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Програма перевірки правопису використ. записи зі списку контактів"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вібр при натиску клав."</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук при натиску клав."</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Сплив. при нат.клав."</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Виправлення тексту"</string>
     <string name="misc_category" msgid="6894192814868233453">"Інші опції"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Розширені налаштування"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Налаштування для досвідчених користувачів"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Налаштування для досвідчених користувачів"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Інші методи введення"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Клавіша зміни мови дозволяє змінювати методи введення"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Заблок.клавішу зміни мови"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Затримка клавіши закриття"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без затримки"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"За умовчанням"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Завжди показувати"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Показувати в книжковому режимі"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Завжди ховати"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Показ. клав. налашт."</string>
     <string name="auto_correction" msgid="4979925752001319458">"Автомат. виправлення"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Пробіл і пунктуація автоматично виправляють слова з помилками"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Вимк."</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : збережено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Іти"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далі"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Назад"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Надісл."</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"Алфавіт"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст не введено"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Код клавіші – %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Клавіша Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift увімкнено"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps Lock увімкнено"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift увімкнено (швидко торкніться, щоб вимкнути)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock увімкнено (швидко торкніться, щоб вимкнути)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Клавіша Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Символи"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Літери"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Голосовий ввід"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Смайлик"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Клавіша Return"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Кома"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Крапка"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Ліва дужка"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Права дужка"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Двокрапка"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Крапка з комою"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Знак оклику"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Знак питання"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Подвійні лапки"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Одинарні лапки"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Крапка"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Квадратний корінь"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Пі"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Дельта"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Торговельна марка"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Через"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Зірочка"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Решітка"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Три крапки"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Нижні подвійні лапки"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Голос. ввід"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Голос. ввід наразі не підтрим. для вашої мови, але можна користуватися англійською."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Голосовий ввід використовує розпізнавання мовлення Google. Застосовується "<a href="http://m.google.com/privacy">"Політика конфіденційності для мобільних пристроїв"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Щоб вимкнути голосовий ввід, перейдіть до налаштувань методів введення."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Щоб використовувати голосовий ввід, натисніть кнопку мікрофона."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Диктуйте"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Працює"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Помилка. Спробуйте ще раз."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Неможл. під\'єднатися"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Помилка. Забагато продикт."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Проблема з аудіо"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Помилка сервера"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Не чути диктув."</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Збігів не знайдено"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Голос. пошук не встановлено"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Підк:"</b>" горт. на клавіат., щоб продикт."</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Підказка:"</b>" наступного разу продикт. знаки пункт. такі як \"крапка\", \"кома\" чи \"знак пит\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Скасувати"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift увімкнено"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock увімкнено"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift вимкнено"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Режим символів"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Режим букв і цифр"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Режим набору номера"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Режим набору символів"</string>
     <string name="voice_input" msgid="3583258583521397548">"Ключ голосового вводу"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"На основ. клавіатурі"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Символьна клавіатура"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Miкр. на осн. клав."</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Miкр. на симв. клавіат."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голос. ввід вимкнено"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Вибрати метод введення"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Налаштування методів введення"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Мови вводу"</string>
     <string name="select_language" msgid="3693815588777926848">"Мови введення"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Торкн. ще, щоб збер."</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Торкніться знову, щоб зберегти"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Словник доступний"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Увімк. відгуки корист."</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Допоможіть покращ. редактор методу введ., автомат. надсилаючи в Google статистику використ. та звіти про збої."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема клавіатури"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Німецька клавіатура QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Англійська (Великобританія)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Англійська (США)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим вивчення зручності у використанні"</string>
diff --git a/java/res/values-vi/donottranslate-more-keys.xml b/java/res/values-vi/donottranslate-more-keys.xml
new file mode 100644
index 0000000..6ef1c6b
--- /dev/null
+++ b/java/res/values-vi/donottranslate-more-keys.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+1EA3: "ả" LATIN SMALL LETTER A WITH HOOK ABOVE
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+1EA1: "ạ" LATIN SMALL LETTER A WITH DOT BELOW
+         U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+         U+1EB1: "ằ" LATIN SMALL LETTER A WITH BREVE AND GRAVE
+         U+1EAF: "ắ" LATIN SMALL LETTER A WITH BREVE AND ACUTE
+         U+1EB3: "ẳ" LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+         U+1EB5: "ẵ" LATIN SMALL LETTER A WITH BREVE AND TILDE
+         U+1EB7: "ặ" LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+1EA7: "ầ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+         U+1EA5: "ấ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+         U+1EA9: "ẩ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+         U+1EAB: "ẫ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+         U+1EAD: "ậ" LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E1;,&#x1EA3;,&#x00E3;,&#x1EA1;,&#x0103;,&#x1EB1;,&#x1EAF;,&#x1EB3;,&#x1EB5;,&#x1EB7;,&#x00E2;,&#x1EA7;,&#x1EA5;,&#x1EA9;,&#x1EAB;,&#x1EAD;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+1EBB: "ẻ" LATIN SMALL LETTER E WITH HOOK ABOVE
+         U+1EBD: "ẽ" LATIN SMALL LETTER E WITH TILDE
+         U+1EB9: "ẹ" LATIN SMALL LETTER E WITH DOT BELOW
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+1EC1: "ề" LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+         U+1EBF: "ế" LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+         U+1EC3: "ể" LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+         U+1EC5: "ễ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+         U+1EC7: "ệ" LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW -->
+    <string name="more_keys_for_e">&#x00E8;,&#x00E9;,&#x1EBB;,&#x1EBD;,&#x1EB9;,&#x00EA;,&#x1EC1;,&#x1EBF;,&#x1EC3;,&#x1EC5;,&#x1EC7;</string>
+    <!-- U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+1EC9: "ỉ" LATIN SMALL LETTER I WITH HOOK ABOVE
+         U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+         U+1ECB: "ị" LATIN SMALL LETTER I WITH DOT BELOW -->
+    <string name="more_keys_for_i">&#x00EC;,&#x00ED;,&#x1EC9;,&#x0129;,&#x1ECB;</string>
+    <!-- U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+1ECF: "ỏ" LATIN SMALL LETTER O WITH HOOK ABOVE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+1ECD: "ọ" LATIN SMALL LETTER O WITH DOT BELOW
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+1ED3: "ồ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+         U+1ED1: "ố" LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+         U+1ED5: "ổ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+         U+1ED7: "ỗ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+         U+1ED9: "ộ" LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+         U+01A1: "ơ" LATIN SMALL LETTER O WITH HORN
+         U+1EDD: "ờ" LATIN SMALL LETTER O WITH HORN AND GRAVE
+         U+1EDB: "ớ" LATIN SMALL LETTER O WITH HORN AND ACUTE
+         U+1EDF: "ở" LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+         U+1EE1: "ỡ" LATIN SMALL LETTER O WITH HORN AND TILDE
+         U+1EE3: "ợ" LATIN SMALL LETTER O WITH HORN AND DOT BELOW -->
+    <string name="more_keys_for_o">&#x00F2;,&#x00F3;,&#x1ECF;,&#x00F5;,&#x1ECD;,&#x00F4;,&#x1ED3;,&#x1ED1;,&#x1ED5;,&#x1ED7;,&#x1ED9;,&#x01A1;,&#x1EDD;,&#x1EDB;,&#x1EDF;,&#x1EE1;,&#x1EE3;</string>
+    <!-- U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+1EE7: "ủ" LATIN SMALL LETTER U WITH HOOK ABOVE
+         U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+         U+1EE5: "ụ" LATIN SMALL LETTER U WITH DOT BELOW
+         U+01B0: "ư" LATIN SMALL LETTER U WITH HORN
+         U+1EEB: "ừ" LATIN SMALL LETTER U WITH HORN AND GRAVE
+         U+1EE9: "ứ" LATIN SMALL LETTER U WITH HORN AND ACUTE
+         U+1EED: "ử" LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+         U+1EEF: "ữ" LATIN SMALL LETTER U WITH HORN AND TILDE
+         U+1EF1: "ự" LATIN SMALL LETTER U WITH HORN AND DOT BELOW -->
+    <string name="more_keys_for_u">&#x00F9;,&#x00FA;,&#x1EE7;,&#x0169;,&#x1EE5;,&#x01B0;,&#x1EEB;,&#x1EE9;,&#x1EED;,&#x1EEF;,&#x1EF1;</string>
+    <!-- U+1EF3: "ỳ" LATIN SMALL LETTER Y WITH GRAVE
+         U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+1EF7: "ỷ" LATIN SMALL LETTER Y WITH HOOK ABOVE
+         U+1EF9: "ỹ" LATIN SMALL LETTER Y WITH TILDE
+         U+1EF5: "ỵ" LATIN SMALL LETTER Y WITH DOT BELOW -->
+    <string name="more_keys_for_y">&#x1EF3;,&#x00FD;,&#x1EF7;,&#x1EF9;,&#x1EF5;</string>
+    <!-- U+0111: "đ" LATIN SMALL LETTER D WITH STROKE -->
+    <string name="more_keys_for_d">&#x0111;</string>
+</resources>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index e602b49..f373729 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -21,12 +21,14 @@
 <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">"Bàn phím Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Bàn phím Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Cài đặt bàn phím Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Tùy chọn nhập"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Dịch vụ sửa chính tả của Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Trình kiểm tra chính tả Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Trình kiểm tra chính tả Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Cài đặt kiểm tra chính tả"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Sử dụng dữ liệu gần"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Dùng thuật toán gần, như của bàn phím để k.tra chính tả"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Tra cứu tên liên hệ"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Trình kiểm tra chính tả sử dụng các mục nhập từ danh sách liên hệ của bạn"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rung khi nhấn phím"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Âm thanh khi nhấn phím"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Cửa sổ bật lên khi nhấn phím"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Sửa văn bản"</string>
     <string name="misc_category" msgid="6894192814868233453">"Tùy chọn khác"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Cài đặt nâng cao"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Tùy chọn cho người dùng chuyên gia"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Tùy chọn dành cho chuyên gia"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Phương thức nhập khác"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Khóa chuyển ngôn ngữ bao gồm cả các phương thức nhập liệu khác"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Bỏ khóa chuyển ngôn ngữ"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Loại bỏ hiển thị phím trễ"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Không có tgian trễ"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Mặc định"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Luôn hiển thị"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Hiển thị trên chế độ khổ đứng"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Luôn ẩn"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Hiển thị phím cài đặt"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Tự động sửa"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Phím cách và dấu câu tự động sửa từ nhập sai"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Tắt"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Đã lưu"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Tìm"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tiếp theo"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Trước"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Xong"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Gửi"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Không có ký tự nào được nhập"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Mã phím %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Đã bật Shift"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Đã bật viết hoa"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift đang bật (bấm để tắt)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock đang bật (bấm để tắt)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Xóa"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Biểu tượng"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Chữ cái"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Nhập dữ liệu bằng giọng nói"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Mặt cười"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Quay lại"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Dấu phẩy"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Dấu chấm"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"Dấu ngoặc trái"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"Dấu ngoặc phải"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Dấu hai chấm"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Dấu chấm phẩy"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Dấu hỏi chấm"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Dấu chấm hỏi"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Dấu ngoặc kép"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Dấu nháy đơn"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Dấu chấm"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Dấu khai căn"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Số Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Thương hiệu"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Dấu phần trăm"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Dấu sao"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Dấu thăng"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Dấu ba chấm"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Dấu nháy kép dưới"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Nhập liệu bằng giọng nói"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Nhập liệu bằng giọng nói hiện không được hỗ trợ cho ngôn ngữ của bạn nhưng hoạt động với ngôn ngữ tiếng Anh."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Nhập liệu bằng giọng nói sử dụng nhận dạng giọng nói của Google. Áp dụng "<a href="http://m.google.com/privacy">"Chính sách bảo mật dành cho điện thoại di động"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Để tắt nhập liệu bằng giọng nói, đi tới cài đặt phương pháp nhập liệu."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Để sử dụng nhập liệu bằng giọng nói, hãy nhấn nút micrô."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Nói ngay"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Đang hoạt động"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Lỗi. Vui lòng thử lại."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Không thể kết nối"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Lỗi, quá nhiều câu thoại."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Sự cố âm thanh"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Lỗi máy chủ"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Không nghe thấy tiếng nói nào"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Không tìm thấy kết quả phù hợp"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Tìm kiếm bằng giọng nói chưa được cài đặt"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Gợi ý:"</b>" Trượt qua bàn phím để nói"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Gợi ý:"</b>" Lần tới, thử nói dấu câu như \"dấu chấm\", \"dấu phẩy\" hoặc \"dấu hỏi\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Hủy"</string>
-    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Đã bật Shift"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Đã bật Caps lock"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Đã tắt Shift"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Chế độ biểu tượng"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Chế độ chữ cái"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Chế độ điện thoại"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Chế độ biểu tượng điện thoại"</string>
     <string name="voice_input" msgid="3583258583521397548">"Khóa nhập giọng nói"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Trên bàn phím chính"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Trên bàn phím biểu tượng"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micrô trên bàn phím chính"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micrô trên bàn phím biểu tượng"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Nhập liệu bằng giọng nói đã bị tắt"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Chọn phương thức nhập"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Định cấu hình phương thức nhập"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Ngôn ngữ nhập"</string>
     <string name="select_language" msgid="3693815588777926848">"Ngôn ngữ nhập"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Chạm lại để lưu"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Có sẵn từ điển"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Bật phản hồi của người dùng"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Giúp nâng cao trình chỉnh sửa phương thức nhập này bằng cách tự động gửi thống kê sử dụng và báo cáo sự cố cho Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Chủ đề bàn phím"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Bàn phím QWERTY tiếng Đức"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Tiếng Anh (Anh)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Tiếng Anh (Mỹ)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Chế độ nghiên cứu tính khả dụng"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index c3adf23..876056b 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android 键盘"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 键盘 (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 键盘设置"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 更正"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 拼写检查工具"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 拼写检查工具 (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"拼写检查设置"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"使用邻近度数据"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"使用类似键盘的邻近度算法进行拼写检查"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找联系人姓名"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼写检查工具会使用您的联系人列表中的条目"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按键时振动"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按键时播放音效"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"按键时显示弹出窗口"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"文本更正"</string>
     <string name="misc_category" msgid="6894192814868233453">"其他选项"</string>
     <string name="advanced_settings" msgid="362895144495591463">"高级设置"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"适合于资深用户的选项"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"高级选项"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"切换到其他输入法"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"语言切换键也可用于切换其他输入法"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"隐藏语言切换键"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"关闭弹出键时的延迟"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"无延迟"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"默认"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"始终显示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"以纵向模式显示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"始终隐藏"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"显示设置键"</string>
     <string name="auto_correction" msgid="4979925752001319458">"自动更正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"按空格键和标点可自动更正错别字"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"关闭"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已保存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"开始"</string>
     <string name="label_next_key" msgid="362972844525672568">"下一步"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"后退"</string>
     <string name="label_done_key" msgid="2441578748772529288">"完成"</string>
     <string name="label_send_key" msgid="2815056534433717444">"发送"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"未输入文字"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"键码为 %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift 已启用"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Caps lock 已启用"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 模式已启用(点按即可停用)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"大写锁定已启用(点按即可停用)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"删除"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"符号"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"字母"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"语音输入"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"笑脸"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"返回"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"逗号"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"句号"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"左括号"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"右括号"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"冒号"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"分号"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"感叹号"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"问号"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"双引号"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"单引号"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"点"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"平方根"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"圆周率"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"商标符号"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"百分号"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"星号"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"井号"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"省略号"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"低双引号"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"语音输入"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"语音输入功能当前还不支持您的语言,您只能输入英语语音。"</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"语音输入采用了 Google 的语音识别技术,因此请遵守"<a href="http://m.google.com/privacy">"“Google 移动”隐私权政策"</a>"。"</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"要关闭语音输入功能,请转至输入法设置。"</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"要使用语音输入功能,请按“麦克风”按钮。"</string>
-    <string name="voice_listening" msgid="467518160751321844">"请开始说话"</string>
-    <string name="voice_working" msgid="6666937792815731889">"正在处理"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"出错,请重试。"</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"无法连接"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"出错,语音过长。"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"音频问题"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"服务器出错"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"未听到语音"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"未找到匹配项"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"未安装语音搜索"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"提示:"</b>"在键盘上滑动手指可激活语音功能"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"提示:"</b>"稍后,请尝试使用语音输入标点符号,如“句号”、“逗号”或“问号”。"</string>
-    <string name="cancel" msgid="6830980399865683324">"取消"</string>
-    <string name="ok" msgid="7898366843681727667">"确定"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift 模式已启用"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"大写锁定已启用"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift 模式已停用"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"符号模式"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"字母模式"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"电话模式"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"电话符号模式"</string>
     <string name="voice_input" msgid="3583258583521397548">"语音输入键"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"主键盘上"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"符号键盘上"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"主键盘上的麦克风"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"符号键盘上的麦克风"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"语音输入功能已停用"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"选择输入法"</string>
     <string name="configure_input_method" msgid="373356270290742459">"配置输入法"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"输入语言"</string>
     <string name="select_language" msgid="3693815588777926848">"输入语言"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← 再次触摸即可保存"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"提供字典"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"启用用户反馈"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"自动向 Google 发送使用情况统计信息和崩溃报告,帮助改进该输入法编辑器。"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"键盘主题"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"德语 QWERTY 键盘"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"英语(英国)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"英语(美国)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"可用性研究模式"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 0542efc..88e53b4 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -21,12 +21,14 @@
 <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">"Android 鍵盤"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 鍵盤 (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 鍵盤設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 拼字修正服務"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 拼字檢查"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 拼字檢查 (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"拼字檢查設定"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"使用鄰近資料"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"運用類似鍵盤的鄰近演算法進行拼字檢查"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查詢聯絡人姓名"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人清單項目"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵時播放音效"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"按鍵時顯示彈出式視窗"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"文字修正"</string>
     <string name="misc_category" msgid="6894192814868233453">"其他選項"</string>
     <string name="advanced_settings" msgid="362895144495591463">"進階設定"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"提供給專業使用者的選項"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"進階選項"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"切換至其他輸入法"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"使語言切換鍵包含其他輸入法"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"隱藏語言切換鍵"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"關閉彈出式鍵盤的延遲時間"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"不延遲"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"預設"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"一律顯示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"以垂直模式顯示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"永遠隱藏"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"顯示設定鍵"</string>
     <string name="auto_correction" msgid="4979925752001319458">"自動修正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"按空白鍵或標點符號時,自動修正前面的錯字"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"關閉"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"開始"</string>
     <string name="label_next_key" msgid="362972844525672568">"繼續"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"上一步"</string>
     <string name="label_done_key" msgid="2441578748772529288">"完成"</string>
     <string name="label_send_key" msgid="2815056534433717444">"傳送"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"未輸入文字"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"按鍵代碼 %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift 鍵"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"Shift 鍵已啟用"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"大寫鎖定已啟用"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 鍵已開啟 (輕按即可停用)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"大寫鎖定已開啟 (輕按即可停用)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"刪除"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"符號"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"字母"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"語音輸入"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"笑臉"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"返回"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"逗號"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"句號"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"左括弧"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"右括弧"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"冒號"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"分號"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"驚嘆號"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"問號"</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"雙引號"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"單引號"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"點"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"平方根"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"圓周率"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"商標"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"百分比"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"星號"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"井字鍵"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"省略符號"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"下雙引號"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"語音輸入"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"語音輸入目前不支援您的語言,但是可以辨識英文。"</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"語音輸入使用 Google 的語音辨識功能,並遵循《"<a href="http://m.google.com/privacy">"行動服務隱私權政策"</a>"》。"</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"如要關閉語音輸入,請前往輸入法設定。"</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"如要使用語音輸入,請按下麥克風按鈕。"</string>
-    <string name="voice_listening" msgid="467518160751321844">"請說話"</string>
-    <string name="voice_working" msgid="6666937792815731889">"辨識中"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"發生錯誤,請再試一次。"</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"無法連線"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"錯誤:語音內容過長。"</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"音訊問題"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"伺服器錯誤"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"沒有聽到任何聲音"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"找不到相符的項目"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"未安裝語音搜尋"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"提示:"</b>"滑過鍵盤即可說話"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"提示:"</b>"下次可嘗試說出標點符號,例如「句號」、「逗號」或「問號」。"</string>
-    <string name="cancel" msgid="6830980399865683324">"取消"</string>
-    <string name="ok" msgid="7898366843681727667">"確定"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift 鍵已啟用"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"大寫鎖定已啟用"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift 鍵已停用"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"符號模式"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"字母模式"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"撥號模式"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"撥號符號模式"</string>
     <string name="voice_input" msgid="3583258583521397548">"語音輸入按鍵"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"主鍵盤上"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"符號鍵盤上"</string>
@@ -135,16 +108,15 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"主鍵盤上的麥克風"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"符號鍵盤上的麥克風"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"語音輸入已停用"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"選擇輸入法"</string>
     <string name="configure_input_method" msgid="373356270290742459">"設定輸入法"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"輸入語言"</string>
     <string name="select_language" msgid="3693815588777926848">"輸入語言"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← 再次輕觸即可儲存"</string>
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"可使用字典"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"啟用使用者意見回饋"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"自動將使用統計資料和當機報告傳送給 Google,協助改善這個輸入法編輯器。"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"鍵盤主題"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"德文 QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"英文 (英式)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"英文 (美式)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"使用性研究模式"</string>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 072d17c..79b1f25 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -21,12 +21,14 @@
 <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">"Ikhibhodi ye-Android"</string>
+    <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Ikhibhodi ye-Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Izilungiselelo zekhibhodi ye-Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Okukhethwa kukho kokungenayo"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Ukulungisa kwe-Android"</string>
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Isihloli sokupela se-Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Isihloli sokupela se-Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Izilungiselelo zokuhlola ukupela"</string>
-    <string name="use_proximity_option_title" msgid="7469233942295924620">"Sebenzisa imininingo ye-proximity"</string>
-    <string name="use_proximity_option_summary" msgid="2857708859847261945">"Sebenzisa i-proximity algorithm efana ne-keyboard ukuhlola ukupela"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Bheka amagama woxhumana nabo"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Isihloli sokupela sisebenzisa okungenayo kusuka kuhlu lalabo oxhumana nabo"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Dlidlizelisa ngokucindezela inkinobho"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Umsindo wokucindezela ukhiye"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ugaxekile ngokucindezela ukhiye"</string>
@@ -34,7 +36,10 @@
     <string name="correction_category" msgid="2236750915056607613">"Ukulungiswa kombhalo"</string>
     <string name="misc_category" msgid="6894192814868233453">"Okunye okukhethwa kukho"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Izilungiselelo ezithuthukisiwe"</string>
-    <string name="advanced_settings_summary" msgid="5193513161106637254">"Okukhethwa kukho kompetha babasebenzi"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Izinketho zezingcwenti"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Shintshela kwezinye izindlela zokungena"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Ukhiye wokushintsha ulimi ubandakanya ezinye izindlela zokungenayo"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Ukhiye wokushintshela wokuvimbela ulimi"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Ukuvela kokhiye cashisa ukulibazisa"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Cha ukulibazisa"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Okuzenzakalelayo"</string>
@@ -50,7 +55,6 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Bonisa njalo"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Bonisa kwimodi emile"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Fihla njalo"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"Bonisa ukhiye wezilungiselelo"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Ukulungisa okuzenzakalelayo"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Ibha yesikhala nokubhala ngamagama amakhulu kulungisa amaphutha amagama athayiphwe kabi"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Valiwe"</string>
@@ -64,6 +68,7 @@
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Kulondoloziwe"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Iya"</string>
     <string name="label_next_key" msgid="362972844525672568">"Okulandelayo"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Eledlule"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Kwenziwe"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Thumela"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
@@ -76,8 +81,8 @@
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Awukho umbhalo ofakiwe"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Ikhodi yokhiye %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="954941524766465022">"I-Shift inikwe amandla"</string>
-    <string name="spoken_description_caps_lock" msgid="5660626444912131764">"Ukunika amandla ukhiye wombhalo ngamagama amakhulu"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"U-Shift uvuliwe (thepha ukuwuvimbela)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Ofeleba bavuliwe (thepha ukubavimbela)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Susa"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Amasimbuli"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Imbhalo"</string>
@@ -88,46 +93,14 @@
     <string name="spoken_description_mic" msgid="615536748882611950">"Okungenayo kwezwi"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Ubuso-obumomothekayo"</string>
     <string name="spoken_description_return" msgid="8178083177238315647">"Buyisela"</string>
-    <string name="spoken_description_comma" msgid="4970844442999724586">"Ikhefu"</string>
-    <string name="spoken_description_period" msgid="5286614628077903945">"Isikhathi"</string>
-    <string name="spoken_description_left_parenthesis" msgid="8524822120595052415">"ama-parenthesis esobunxele"</string>
-    <string name="spoken_description_right_parenthesis" msgid="1085757995851933164">"I-parenthesis yesokudla"</string>
-    <string name="spoken_description_colon" msgid="4312420908484277077">"Ikholoni"</string>
-    <string name="spoken_description_semicolon" msgid="37737920987155179">"Ikhefanangqi"</string>
-    <string name="spoken_description_exclamation_mark" msgid="2625684427460737157">"Uphawu lokumemeza"</string>
-    <string name="spoken_description_question_mark" msgid="7074097784255379666">"Imaki yombuzo."</string>
-    <string name="spoken_description_double_quote" msgid="5485320575389905967">"Ukusho kabili"</string>
-    <string name="spoken_description_single_quote" msgid="4451320362665463938">"Isibizo esisodwa"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Icashazi"</string>
-    <string name="spoken_description_square_root" msgid="190595160284757811">"Impande yesikwele"</string>
-    <string name="spoken_description_pi" msgid="4554418247799952239">"Pi"</string>
-    <string name="spoken_description_delta" msgid="3607948313655721579">"i-Delta"</string>
-    <string name="spoken_description_trademark" msgid="475877774077871369">"Uphawu lomkhiqizo"</string>
-    <string name="spoken_description_care_of" msgid="7492800237237796530">"Ukunakekela ko"</string>
-    <string name="spoken_description_star" msgid="1009742725387231977">"Inkanyezi"</string>
-    <string name="spoken_description_pound" msgid="5530577649206922631">"Iphawundi"</string>
-    <string name="spoken_description_ellipsis" msgid="1687670869947652062">"Ellipsis"</string>
-    <string name="spoken_description_low_double_quote" msgid="3551394572784840975">"Isilinganiso esikabili esiphansi"</string>
-    <string name="voice_warning_title" msgid="4419354150908395008">"Okufakwa ngezwi"</string>
-    <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Okufakwa ngezwi akusekelwa olimini lwakho, kodwa kuyasebenza nge-English."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Okufakwayo ngezwi kusebenzisa ukufanisa izwi kwe-Google. "<a href="http://m.google.com/privacy">"Inqubomgomo Yobumfihlo Yefoni"</a>" iyasebenza."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Ukuvala okufakwayo ngezwi, iya kuzilungiselelo zendlela yokufakwayo"</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Ukusebenzisa okufakwayo ngezwi, cindezela inkinobho yemakrofoni."</string>
-    <string name="voice_listening" msgid="467518160751321844">"Khuluma manje"</string>
-    <string name="voice_working" msgid="6666937792815731889">"Kuyasebenza"</string>
-    <string name="voice_initializing" msgid="661962047129906646"></string>
-    <string name="voice_error" msgid="5140896300312186162">"Iphutha. Sicela uzame futhi."</string>
-    <string name="voice_network_error" msgid="6649556447401862563">"Ayikwazanga ukuxhuma"</string>
-    <string name="voice_too_much_speech" msgid="5746973620134227376">"Iphutha, kunamagama amaningi."</string>
-    <string name="voice_audio_error" msgid="5072707727016414454">"Inkinga yomsindo"</string>
-    <string name="voice_server_error" msgid="7807129913977261644">"Iphutha leseva"</string>
-    <string name="voice_speech_timeout" msgid="8461817525075498795">"Awekho amagama azwakele"</string>
-    <string name="voice_no_match" msgid="4285117547030179174">"Akukho okufanayo okutholiwe"</string>
-    <string name="voice_not_installed" msgid="5552450909753842415">"Ukusesha ngezwi akufakiwe"</string>
-    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Isexwayiso:"</b>"Shintshela kwikhibhodi ukuze ukhulume"</string>
-    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Isixwayiso: "</b>"Esikhathini esilandelayo, zama ukukhuluma izimpimiselo ezinjengo \"isikhathi, \"ikhefu\" noma \"uphawu lombuzo\"."</string>
-    <string name="cancel" msgid="6830980399865683324">"Khansela"</string>
-    <string name="ok" msgid="7898366843681727667">"KULUNGILE"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"U-Shift uvunyelwe"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Ofeleba bavunyelwe"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"U-Shift uvimbelwe"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Imodi yezimpawu"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Imodi yezinhlamvu"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Imodi yefoni"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Imodi yezimpawu zefoni"</string>
     <string name="voice_input" msgid="3583258583521397548">"Inkinobho yokufaka izwi"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Kwikhibhodi eyisisekelo"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Ikhibhodi yezimpawu"</string>
@@ -135,16 +108,14 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"I-mic kwikhibhodi eyisisekelo"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Ikhibhodi yezimpawu ze-mic"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Okufakwayo ngezwi kuvinjelwe"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Khetha indlela yokufaka"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Misa izindlela zokufakwayo"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Izilimi zokufakwayo"</string>
     <string name="select_language" msgid="3693815588777926848">"Izilimi zokufakwayo"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Thinta futhi ukulondoloza"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Thinta futhi ukuze ulondoloze"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Isichazamazwi siyatholakala"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Vumela impendulo yomsebenzisi"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"Siza ukuthuthukisa lo mhleli wendlela yokufakwa ngokusithumela ngokuzenzakalela izibalo zokusetshenziswa nokukhubeka ku-Google."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Indikimba yekhibhodi"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"i-QWERTY yesi-German"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"i-English(UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"i-English (US)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Imodi yesitadi yokusebenziseka"</string>
diff --git a/java/res/values-zz/donottranslate-more-keys.xml b/java/res/values-zz/donottranslate-more-keys.xml
new file mode 100644
index 0000000..eb984a4
--- /dev/null
+++ b/java/res/values-zz/donottranslate-more-keys.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+         U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+         U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+         U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+         U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+         U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+         U+00E6: "æ" LATIN SMALL LETTER AE
+         U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+         U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+         U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+         U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+    <string name="more_keys_for_a">&#x00E0;,&#x00E1;,&#x00E2;,&#x00E3;,&#x00E4;,&#x00E5;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;,&#x0103;,&#x0105;,&#x00AA;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+         U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+         U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+         U+0115: "ĕ" LATIN SMALL LETTER E WITH BREVE
+         U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+         U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+         U+011B: "ě" LATIN SMALL LETTER E WITH CARON -->
+    <string name="more_keys_for_e">&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0113;,&#x0115;,&#x0117;,&#x0119;,&#x011B;</string>
+    <!-- U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+         U+012D: "ĭ" LATIN SMALL LETTER I WITH BREVE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+         U+0133: "ij" LATIN SMALL LIGATURE IJ -->
+    <string name="more_keys_for_i">&#x00EC;,&#x00ED;,&#x00EE;,&#x00EF;,&#x0129;,&#x012B;,&#x012D;,&#x012F;,&#x0131;,&#x0133;</string>
+    <!-- U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+         U+014F: "ŏ" LATIN SMALL LETTER O WITH BREVE
+         U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+    <string name="more_keys_for_o">&#x00F2;,&#x00F3;,&#x00F4;,&#x00F5;,&#x00F6;,&#x00F8;,&#x014D;,&#x014F;,&#x0151;,&#x0153;,&#x00BA;</string>
+    <!-- U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+         U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
+         U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+         U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+         U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK -->
+    <string name="more_keys_for_u">&#x00F9;,&#x00FA;,&#x00FB;,&#x00FC;,&#x0169;,&#x016B;,&#x016D;,&#x016F;,&#x0171;,&#x0173;</string>
+    <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
+         U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON
+         U+017F: "ſ" LATIN SMALL LETTER LONG S -->
+    <string name="more_keys_for_s">&#x00DF;,&#x015B;,&#x015D;,&#x015F;,&#x0161;,&#x017F;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+         U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+         U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+         U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+         U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+         U+014B: "ŋ" LATIN SMALL LETTER ENG -->
+    <string name="more_keys_for_n">&#x00F1;,&#x0144;,&#x0146;,&#x0148;,&#x0149;,&#x014B;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
+         U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x0109;,&#x010B;,&#x010D;</string>
+    <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+         U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
+         U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+         U+0133: "ij" LATIN SMALL LIGATURE IJ -->
+    <string name="more_keys_for_y">&#x00FD;,&#x0177;,&#x00FF;,&#x0133;</string>
+    <!-- U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+         U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+         U+00F0: "ð" LATIN SMALL LETTER ETH -->
+    <string name="more_keys_for_d">&#x010F;,&#x0111;,&#x00F0;</string>
+    <!-- U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+         U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+         U+0159: "ř" LATIN SMALL LETTER R WITH CARON -->
+    <string name="more_keys_for_r">&#x0155;,&#x0157;,&#x0159;</string>
+    <!-- U+00FE: "þ" LATIN SMALL LETTER THORN
+         U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+         U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+         U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE -->
+    <string name="more_keys_for_t">&#x00FE;,&#x0163;,&#x0165;,&#x0167;</string>
+    <!-- U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+         U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+         U+017E: "ž" LATIN SMALL LETTER Z WITH CARON -->
+    <string name="more_keys_for_z">&#x017A;,&#x017C;,&#x017E;</string>
+    <!-- U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+         U+0138: "ĸ" LATIN SMALL LETTER KRA -->
+    <string name="more_keys_for_k">&#x0137;,&#x0138;</string>
+    <!-- U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+         U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+         U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+         U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+         U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
+    <string name="more_keys_for_l">&#x013A;,&#x013C;,&#x013E;,&#x0140;,&#x0142;</string>
+    <!-- U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
+         U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+         U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
+         U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA -->
+    <string name="more_keys_for_g">&#x011D;,&#x011F;,&#x0121;,&#x0123;</string>
+    <!-- U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX -->
+    <string name="more_keys_for_h">&#x0125;</string>
+    <!-- U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX -->
+    <string name="more_keys_for_j">&#x0135;</string>
+    <!-- U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX -->
+    <string name="more_keys_for_w">&#x0175;</string>
+</resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 0c9ca4f..b3f30c6 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -18,15 +18,18 @@
     <declare-styleable name="KeyboardTheme">
         <!-- Keyboard style -->
         <attr name="keyboardStyle" format="reference" />
+        <!-- TODO: Get rid of latinKeyboardStyle -->
         <!-- LatinKeyboard style -->
         <attr name="latinKeyboardStyle" format="reference" />
         <!-- KeyboardView style -->
         <attr name="keyboardViewStyle" format="reference" />
-        <!-- MiniKeyboard style -->
-        <attr name="miniKeyboardStyle" format="reference" />
-        <!-- MiniKeyboardView style -->
-        <attr name="miniKeyboardViewStyle" format="reference" />
-        <attr name="miniKeyboardPanelStyle" format="reference" />
+        <!-- LatinKeyboardView style -->
+        <attr name="latinKeyboardViewStyle" format="reference" />
+        <!-- MoreKeysKeyboard style -->
+        <attr name="moreKeysKeyboardStyle" format="reference" />
+        <!-- MoreKeysKeyboardView style -->
+        <attr name="moreKeysKeyboardViewStyle" format="reference" />
+        <attr name="moreKeysKeyboardPanelStyle" format="reference" />
         <!-- Suggestions strip style -->
         <attr name="suggestionsStripBackgroundStyle" format="reference" />
         <attr name="suggestionsViewStyle" format="reference" />
@@ -57,16 +60,16 @@
         <attr name="keyHintLetterRatio" format="float" />
         <!-- Size of the text for hint label, in the proportion of key height. -->
         <attr name="keyHintLabelRatio" format="float" />
-        <!-- Size of the text for upper case letter, in the proportion of key height. -->
-        <attr name="keyUppercaseLetterRatio" format="float" />
+        <!-- Size of the text for shifted letter hint, in the proportion of key height. -->
+        <attr name="keyShiftedLetterHintRatio" format="float" />
         <!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
         <attr name="keyLabelHorizontalPadding" format="dimension" />
         <!-- Top and right padding of hint letter to the edge of the key.-->
         <attr name="keyHintLetterPadding" format="dimension" />
         <!-- Bottom padding of popup hint letter "..." to the edge of the key.-->
         <attr name="keyPopupHintLetterPadding" format="dimension" />
-        <!-- Top and right padding of upper case letter to the edge of the key.-->
-        <attr name="keyUppercaseLetterPadding" format="dimension" />
+        <!-- Top and right padding of shifted letter hint to the edge of the key.-->
+        <attr name="keyShiftedLetterHintPadding" format="dimension" />
 
         <!-- Color to use for the label in a key. -->
         <attr name="keyTextColor" format="color" />
@@ -76,9 +79,9 @@
         <attr name="keyHintLetterColor" format="color" />
         <!-- Key hint label color -->
         <attr name="keyHintLabelColor" format="color" />
-        <!-- Upper case letter colors -->
-        <attr name="keyUppercaseLetterInactivatedColor" format="color" />
-        <attr name="keyUppercaseLetterActivatedColor" format="color" />
+        <!-- Shifted letter hint colors -->
+        <attr name="keyShiftedLetterHintInactivatedColor" format="color" />
+        <attr name="keyShiftedLetterHintActivatedColor" format="color" />
 
         <!-- Layout resource for key press feedback.-->
         <attr name="keyPreviewLayout" format="reference" />
@@ -100,6 +103,8 @@
         <attr name="keyPreviewHeight" format="dimension" />
         <!-- Size of the text for key press feedback popup, int the proportion of key height -->
         <attr name="keyPreviewTextRatio" format="float" />
+        <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
+        <attr name="keyPreviewLingerTimeout" format="integer" />
 
         <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
         <attr name="verticalCorrection" format="dimension" />
@@ -109,7 +114,7 @@
 
         <attr name="shadowColor" format="color" />
         <attr name="shadowRadius" format="float" />
-        <attr name="backgroundDimAmount" format="float" />
+        <attr name="backgroundDimAlpha" format="integer" />
 
         <attr name="keyTextStyle" format="enum">
             <!-- This should be aligned with Typeface.NORMAL etc. -->
@@ -120,6 +125,43 @@
         </attr>
     </declare-styleable>
 
+    <declare-styleable name="LatinKeyboardView">
+        <attr name="autoCorrectionSpacebarLedEnabled" format="boolean" />
+        <attr name="autoCorrectionSpacebarLedIcon" format="reference" />
+        <!-- Size of the text for spacebar language label, in the proportion of key height. -->
+        <attr name="spacebarTextRatio" format="fraction" />
+        <attr name="spacebarTextColor" format="color" />
+        <attr name="spacebarTextShadowColor" format="color" />
+        <!-- Fadeout animator for spacebar language label. -->
+        <attr name="languageOnSpacebarFinalAlpha" format="integer" />
+        <attr name="languageOnSpacebarFadeoutAnimator" format="reference" />
+        <!-- Fadeout and fadein animator for altCodeWhileTyping keys. -->
+        <attr name="altCodeKeyWhileTypingFadeoutAnimator" format="reference" />
+        <attr name="altCodeKeyWhileTypingFadeinAnimator" format="reference" />
+        <!-- Key detection hysteresis distance. -->
+        <attr name="keyHysteresisDistance" format="dimension" />
+        <!-- Touch noise threshold time in millisecond -->
+        <attr name="touchNoiseThresholdTime" format="integer" />
+        <!-- Touch noise threshold distance in millimeter -->
+        <attr name="touchNoiseThresholdDistance" format="dimension" />
+        <!-- Sliding key input enable -->
+        <attr name="slidingKeyInputEnable" format="boolean" />
+        <!-- Key repeat start timeout -->
+        <attr name="keyRepeatStartTimeout" format="integer" />
+        <!-- Key repeat interval in millisecond. -->
+        <attr name="keyRepeatInterval" format="integer" />
+        <!-- Long press timeout of letter key in millisecond. -->
+        <attr name="longPressKeyTimeout" format="integer" />
+        <!-- Long press timeout of shift key in millisecond. -->
+        <attr name="longPressShiftKeyTimeout" format="integer" />
+        <!-- Long press timeout of space key in millisecond. -->
+        <attr name="longPressSpaceKeyTimeout" format="integer" />
+        <!-- Ignore special key timeout while typing in millisecond. -->
+        <attr name="ignoreAltCodeKeyTimeout" format="integer" />
+        <!-- More keys keyboard will shown at touched point. -->
+        <attr name="showMoreKeysKeyboardAtTouchedPoint" format="boolean" />
+    </declare-styleable>
+
     <declare-styleable name="SuggestionsView">
         <attr name="suggestionStripOption" format="integer">
             <!-- This should be aligned with SuggestionsViewParams.AUTO_CORRECT_* and etc. -->
@@ -127,9 +169,11 @@
             <flag name="autoCorrectUnderline" value="0x02" />
             <flag name="validTypedWordBold" value="0x04" />
         </attr>
+        <attr name="colorValidTypedWord" format="color" />
         <attr name="colorTypedWord" format="color" />
         <attr name="colorAutoCorrect" format="color" />
         <attr name="colorSuggested" format="color" />
+        <attr name="alphaValidTypedWord" format="integer" />
         <attr name="alphaTypedWord" format="integer" />
         <attr name="alphaAutoCorrect" format="integer" />
         <attr name="alphaSuggested" format="integer" />
@@ -158,16 +202,13 @@
         <!-- Default height of a row (key height + vertical gap), in pixels or percentage of
              keyboard height. -->
         <attr name="rowHeight" format="dimension|fraction" />
-        <!-- Default horizontal gap between keys. -->
+        <!-- Default horizontal gap between keys, in pixels or percentage of keyboard width. -->
         <attr name="horizontalGap" format="dimension|fraction" />
-        <!-- Default vertical gap between rows of keys. -->
+        <!-- Default vertical gap between rows of keys, in pixels or percentage of keyboard
+             height. -->
         <attr name="verticalGap" format="dimension|fraction" />
         <!-- More keys keyboard layout template -->
         <attr name="moreKeysTemplate" format="reference" />
-        <!-- Locale of the keyboard layout -->
-        <attr name="keyboardLocale" format="string" />
-        <!-- True if the keyboard is Right-To-Left -->
-        <attr name="isRtlKeyboard" format="boolean" />
         <!-- Icon set for key top and key preview. -->
         <attr name="iconShiftKey" format="reference" />
         <attr name="iconDeleteKey" format="reference" />
@@ -178,15 +219,28 @@
         <attr name="iconTabKey" format="reference" />
         <attr name="iconShortcutKey" format="reference" />
         <attr name="iconShortcutForLabel" format="reference" />
-        <attr name="iconShiftedShiftKey" format="reference" />
+        <attr name="iconSpaceKeyForNumberLayout" format="reference" />
+        <attr name="iconShiftKeyShifted" format="reference" />
+        <attr name="iconDisabledShortcutKey" format="reference" />
         <attr name="iconPreviewTabKey" format="reference" />
+        <attr name="iconLanguageSwitchKey" format="reference" />
+        <attr name="iconZwnjKey" format="reference" />
+        <attr name="iconZwjKey" format="reference" />
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Key">
         <!-- The unicode value that this key outputs. -->
         <attr name="code" format="integer" />
+        <!-- The alternate unicode value that this key outputs while typing. -->
+        <attr name="altCode" format="integer" />
         <!-- The keys to display in the more keys keyboard. -->
         <attr name="moreKeys" format="string" />
+        <!-- The keys to display in the more keys keyboard in addition to moreKeys.
+             The additional more keys are inserted at the '%' markers in the moreKeys if any.
+             They are inserted at the head of moreKeys if none.
+             If there are remaining entries of additionalMoreKeys even after all '%' markers have
+             been replaced, those remaining entries are appended at the end of moreKeys. -->
+        <attr name="additionalMoreKeys" format="string" />
         <!-- Maximum column of more keys keyboard -->
         <attr name="maxMoreKeysColumn" format="integer" />
         <attr name="backgroundType" format="enum">
@@ -194,19 +248,26 @@
             <enum name="normal" value="0" />
             <enum name="functional" value="1" />
             <enum name="action" value="2" />
-            <enum name="sticky" value="3" />
+            <enum name="stickyOff" value="3" />
+            <enum name="stickyOn" value="4" />
         </attr>
-        <!-- Whether long-pressing on this key will make it repeat. -->
-        <attr name="isRepeatable" format="boolean" />
+        <!-- The key action flags. -->
+        <attr name="keyActionFlags" format="integer">
+            <!-- This should be aligned with Key.ACTION_FLAGS_* -->
+            <flag name="isRepeatable" value="0x01" />
+            <flag name="noKeyPreview" value="0x02" />
+            <flag name="altCodeWhileTyping" value="0x04" />
+            <flag name="enableLongPress" value="0x08" />
+        </attr>
         <!-- The string of characters to output when this key is pressed. -->
         <attr name="keyOutputText" format="string" />
         <!-- The label to display on the key. -->
         <attr name="keyLabel" format="string" />
         <!-- The hint label to display on the key in conjunction with the label. -->
         <attr name="keyHintLabel" format="string" />
-        <!-- The key label option. -->
-        <attr name="keyLabelOption" format="integer">
-            <!-- This should be aligned with Key.LABEL_OPTION_* -->
+        <!-- The key label flags. -->
+        <attr name="keyLabelFlags" format="integer">
+            <!-- This should be aligned with Key.LABEL_FLAGS__* -->
             <flag name="alignLeft" value="0x01" />
             <flag name="alignRight" value="0x02" />
             <flag name="alignLeftOfCenter" value="0x08" />
@@ -216,15 +277,28 @@
             <flag name="followKeyLetterRatio" value="0x80" />
             <flag name="followKeyHintLabelRatio" value="0x100" />
             <flag name="hasPopupHint" value="0x200" />
-            <flag name="hasUppercaseLetter" value="0x400" />
+            <flag name="hasShiftedLetterHint" value="0x400" />
             <flag name="hasHintLabel" value="0x800" />
             <flag name="withIconLeft" value="0x1000" />
             <flag name="withIconRight" value="0x2000" />
             <flag name="autoXScale" value="0x4000" />
+            <!-- If true, character case of code, altCode, moreKeys, keyOutputText, keyLabel,
+                 or keyHintLabel will never be subject to change. -->
+            <flag name="preserveCase" value="0x8000" />
+            <!-- If true, use keyShiftedLetterHintActivatedColor for the shifted letter hint and
+                 keyTextInactivatedColor for the primary key top label. -->
+            <flag name="shiftedLetterActivated" value="0x10000" />
+            <!-- If true, use EditorInfo.actionLabel for the key label. -->
+            <flag name="fromCustomActionLabel" value="0x20000" />
+            <!-- If true, disable keyHintLabel. -->
+            <flag name="disableKeyHintLabel" value="0x40000000" />
+            <!-- If true, disable additionalMoreKeys. -->
+            <flag name="disableAdditionalMoreKeys" value="0x80000000" />
         </attr>
         <!-- The icon to display on the key instead of the label. -->
         <attr name="keyIcon" format="enum">
-            <!-- This should be aligned with KeyboardIcons.ICON_* -->
+            <!-- This should be aligned with the KeyboardIconsSet.ICON_* -->
+            <enum name="iconUndefined" value="0" />
             <enum name="iconShiftKey" value="1" />
             <enum name="iconDeleteKey" value="2" />
             <enum name="iconSettingsKey" value="3" />
@@ -234,21 +308,24 @@
             <enum name="iconTabKey" value="7" />
             <enum name="iconShortcutKey" value="8" />
             <enum name="iconShortcutForLabel" value="9" />
+            <enum name="iconSpaceKeyForNumberLayout" value="10" />
+            <enum name="iconShiftKeyShifted" value="11" />
+            <enum name="iconLanguageSwitchKey" value="14" />
+            <enum name="iconZwnjKey" value="15" />
+            <enum name="iconZwjKey" value="16" />
         </attr>
-        <!-- Shift key icon for shifted state -->
-        <attr name="keyIconShifted" format="enum">
-            <!-- This should be aligned with KeyboardIcons.ICON_SHIFTED_* -->
-            <enum name="iconShiftedShiftKey" value="10" />
+        <!-- The icon for disabled key -->
+        <attr name="keyIconDisabled" format="enum">
+            <!-- This should be aligned with the KeyboardIconsSet.ICON_* -->
+            <enum name="iconDisabledShortcutKey" value="12" />
         </attr>
         <!-- The icon to show in the popup preview. -->
         <attr name="keyIconPreview" format="enum">
-            <!-- This should be aligned with KeyboardIcons.ICON_PREVIEW_* -->
-            <enum name="iconPreviewTabKey" value="11" />
+            <!-- This should be aligned with the KeyboardIconsSet.ICON_* -->
+            <enum name="iconPreviewTabKey" value="13" />
         </attr>
         <!-- The key style to specify a set of key attributes defined by <key_style/> -->
         <attr name="keyStyle" format="string" />
-        <!-- The key is enabled and responds on press. -->
-        <attr name="enabled" format="boolean" />
         <!-- Visual insets -->
         <attr name="visualInsetsLeft" format="dimension|fraction" />
         <attr name="visualInsetsRight" format="dimension|fraction" />
@@ -273,6 +350,19 @@
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Case">
+        <!-- This should be aligned with KeyboardSet_Element's elementName. -->
+        <attr name="keyboardSetElement" format="enum|string">
+            <enum name="alphabet" value="0" />
+            <enum name="alphabetManualShifted" value="1" />
+            <enum name="alphabetAutomaticShifted" value="2" />
+            <enum name="alphabetShiftLocked" value="3" />
+            <enum name="alphabetShiftLockShifted" value="4" />
+            <enum name="symbols" value="5" />
+            <enum name="symbolsShifted" value="6"  />
+            <enum name="phone" value="7"  />
+            <enum name="phoneSymbols" value="8"  />
+            <enum name="number" value="9"  />
+        </attr>
         <!-- This should be aligned with KeyboardId.MODE_* -->
         <attr name="mode" format="enum|string">
             <enum name="text" value="0" />
@@ -282,19 +372,14 @@
             <enum name="phone" value="4" />
             <enum name="number" value="5" />
         </attr>
-        <attr name="navigateAction" format="boolean" />
+        <attr name="navigateNext" format="boolean" />
+        <attr name="navigatePrevious" format="boolean" />
         <attr name="passwordInput" format="boolean" />
-        <attr name="hasSettingsKey" format="boolean" />
-        <!-- This should be aligned with KeyboardID.F2KEY_MODE_* -->
-        <attr name="f2KeyMode" format="enum">
-            <enum name="none" value="0" />
-            <enum name="settings" value="1" />
-            <enum name="shortcutIme" value="2" />
-            <enum name="shortcutImeOrSettings" value="3" />
-        </attr>
         <attr name="clobberSettingsKey" format="boolean" />
         <attr name="shortcutKeyEnabled" format="boolean" />
         <attr name="hasShortcutKey" format="boolean" />
+        <attr name="languageSwitchKeyEnabled" format="boolean" />
+        <attr name="isMultiLine" format="boolean" />
         <attr name="imeAction" format="enum">
             <!-- This should be aligned with EditorInfo.IME_ACTION_* -->
             <enum name="actionUnspecified" value="0" />
@@ -305,6 +390,8 @@
             <enum name="actionNext" value="5" />
             <enum name="actionDone" value="6" />
             <enum name="actionPrevious" value="7" />
+            <!--  This should be aligned with KeyboardId.IME_ACTION_* -->
+            <enum name="actionCustomLabel" value="0x100" />
         </attr>
         <attr name="localeCode" format="string" />
         <attr name="languageCode" format="string" />
@@ -316,11 +403,27 @@
         <attr name="parentStyle" format="string" />
     </declare-styleable>
 
-    <declare-styleable name="LatinKeyboard">
-        <attr name="autoCorrectionSpacebarLedEnabled" format="boolean" />
-        <attr name="autoCorrectionSpacebarLedIcon" format="reference" />
-        <attr name="disabledShortcutIcon" format="reference" />
-        <attr name="spacebarTextColor" format="color" />
-        <attr name="spacebarTextShadowColor" format="color" />
+    <declare-styleable name="KeyboardSet">
+        <!-- Disable shortcut key. Shortcut key is enabled by default. -->
+        <attr name="disableShortcutKey" format="boolean" />
+    </declare-styleable>
+
+    <declare-styleable name="KeyboardSet_Element">
+        <!-- This should be aligned with KeyboardId.ELEMENT_* -->
+        <attr name="elementName" format="enum">
+            <enum name="alphabet" value="0" />
+            <enum name="alphabetManualShifted" value="1" />
+            <enum name="alphabetAutomaticShifted" value="2" />
+            <enum name="alphabetShiftLocked" value="3" />
+            <enum name="alphabetShiftLockShifted" value="4" />
+            <enum name="symbols" value="5" />
+            <enum name="symbolsShifted" value="6"  />
+            <enum name="phone" value="7"  />
+            <enum name="phoneSymbols" value="8"  />
+            <enum name="number" value="9"  />
+        </attr>
+        <attr name="elementKeyboard" format="reference"/>
+        <!-- Enable proximity characters correction. Disabled by default. -->
+        <attr name="enableProximityCharsCorrection" format="boolean" />
     </declare-styleable>
 </resources>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 3f676ab..f0b12e9 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -20,14 +20,11 @@
 
 <resources>
     <bool name="config_use_fullscreen_mode">false</bool>
-    <bool name="config_enable_show_settings_key_option">true</bool>
-    <bool name="config_default_show_settings_key">false</bool>
     <bool name="config_enable_show_voice_key_option">true</bool>
     <bool name="config_enable_show_popup_on_keypress_option">true</bool>
     <bool name="config_enable_bigram_suggestions_option">true</bool>
-    <bool name="config_enable_usability_study_mode_option">false</bool>
-    <bool name="config_sliding_key_input_enabled">true</bool>
-    <bool name="config_digit_more_keys_enabled">true</bool>
+    <!-- TODO: Disable the following configuration for production. -->
+    <bool name="config_enable_usability_study_mode_option">true</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
     <bool name="config_default_popup_preview">true</bool>
     <!-- Default value for bigram suggestion: while showing suggestions for a word should we weigh
@@ -38,37 +35,44 @@
     <bool name="config_default_bigram_prediction">false</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_default_vibration_enabled">true</bool>
-    <bool name="config_auto_correction_spacebar_led_enabled">false</bool>
-    <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
-    <bool name="config_show_mini_keyboard_at_touched_point">false</bool>
-    <!-- The language is never displayed if == 0, always displayed if < 0 -->
-    <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
     <integer name="config_delay_update_suggestions">100</integer>
     <integer name="config_delay_update_old_suggestions">300</integer>
     <integer name="config_delay_update_shift_state">100</integer>
-    <integer name="config_duration_of_fadeout_language_on_spacebar">50</integer>
-    <integer name="config_final_fadeout_percentage_of_language_on_spacebar">50</integer>
-    <integer name="config_delay_before_preview">0</integer>
-    <integer name="config_delay_after_preview">70</integer>
-    <integer name="config_mini_keyboard_fadein_anim_time">0</integer>
-    <integer name="config_mini_keyboard_fadeout_anim_time">100</integer>
-    <integer name="config_delay_before_key_repeat_start">400</integer>
-    <integer name="config_key_repeat_interval">50</integer>
+    <integer name="config_language_on_spacebar_final_alpha">128</integer>
+    <integer name="config_more_keys_keyboard_fadein_anim_time">0</integer>
+    <integer name="config_more_keys_keyboard_fadeout_anim_time">100</integer>
     <integer name="config_keyboard_grid_width">32</integer>
     <integer name="config_keyboard_grid_height">16</integer>
+    <integer name="config_double_spaces_turn_into_period_timeout">1100</integer>
+    <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
+    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
+    <integer name="config_max_more_keys_column">5</integer>
+    <!--
+         Configuration for KeyboardView
+    -->
+    <integer name="config_key_preview_linger_timeout">70</integer>
+    <!--
+         Configuration for LatinKeyboardView
+    -->
+    <dimen name="config_key_hysteresis_distance">8.0dp</dimen>
+    <integer name="config_touch_noise_threshold_time">40</integer>
+    <dimen name="config_touch_noise_threshold_distance">12.6dp</dimen>
+    <bool name="config_sliding_key_input_enabled">true</bool>
+    <integer name="config_key_repeat_start_timeout">400</integer>
+    <integer name="config_key_repeat_interval">50</integer>
     <integer name="config_long_press_key_timeout">400</integer>
     <!-- Long pressing shift will invoke caps-lock if > 0, never invoke caps-lock if == 0 -->
     <integer name="config_long_press_shift_key_timeout">1200</integer>
     <!-- Long pressing space will invoke IME switcher if > 0, never invoke IME switcher if == 0 -->
-    <integer name="config_long_press_space_key_timeout">@integer/config_long_press_key_timeout</integer>
-    <integer name="config_touch_noise_threshold_millis">40</integer>
-    <integer name="config_double_spaces_turn_into_period_timeout">1100</integer>
-    <integer name="config_ignore_special_key_timeout">700</integer>
-    <dimen name="config_touch_noise_threshold_distance">2.0mm</dimen>
-    <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_id" translatable="false">5</string>
-    <string name="config_text_size_of_language_on_spacebar" translatable="false">small</string>
-    <integer name="config_max_more_keys_column">5</integer>
+    <integer name="config_long_press_space_key_timeout">
+            @integer/config_long_press_key_timeout</integer>
+    <integer name="config_ignore_alt_code_key_timeout">700</integer>
+    <!-- Showing more keys keyboard, just above the touched point if true, aligned to the key if
+         false -->
+    <bool name="config_show_more_keys_keyboard_at_touched_point">false</bool>
+    <!--
+        Configuration for auto correction
+     -->
     <string-array name="auto_correction_threshold_values" translatable="false">
         <!-- Off, When auto correction setting is Off, this value is not used. -->
         <item></item>
@@ -81,9 +85,11 @@
              will be subject to auto-correction. -->
         <item>0</item>
     </string-array>
-    <!-- Threshold of the normalized score of the best suggestion for the spell checker to declare a word to be "likely" -->
-    <string name="spellchecker_likely_threshold_value" translatable="false">0.11</string>
-    <!-- Threshold of the normalized score of any dictionary lookup to be offered as a suggestion by the spell checker -->
+    <!-- Threshold of the normalized score of the best suggestion for the spell checker to declare
+         a word to be "recommended" -->
+    <string name="spellchecker_recommended_threshold_value" translatable="false">0.11</string>
+    <!-- Threshold of the normalized score of any dictionary lookup to be offered as a suggestion
+         by the spell checker -->
     <string name="spellchecker_suggestion_threshold_value" translatable="false">0.03</string>
     <!--  Screen metrics for logging.
             0 = "mdpi phone screen"
@@ -93,5 +99,4 @@
             4 = ?
     -->
     <integer name="log_screen_metrics">0</integer>
-    <bool name="config_require_umlaut_processing">false</bool>
 </resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index d1067f3..7eb57c3 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -19,15 +19,16 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = row_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">1.285in</dimen>
-    <fraction name="maxKeyboardHeight">50%p</fraction>
+    <!-- Preferable keyboard height in absolute scale: 1.285in -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">205.6dp</dimen>
+    <fraction name="maxKeyboardHeight">46%p</fraction>
     <fraction name="minKeyboardHeight">-61.8%p</fraction>
 
-    <dimen name="popup_key_height">0.330in</dimen>
+    <dimen name="popup_key_height">52.8dp</dimen>
 
-    <dimen name="mini_keyboard_horizontal_edges_padding">16dip</dimen>
-    <dimen name="mini_keyboard_key_horizontal_padding">8dip</dimen>
+    <dimen name="more_keys_keyboard_horizontal_edges_padding">16dp</dimen>
+    <dimen name="more_keys_keyboard_key_horizontal_padding">8dp</dimen>
 
     <fraction name="keyboard_top_padding">1.556%p</fraction>
     <fraction name="keyboard_bottom_padding">4.669%p</fraction>
@@ -35,7 +36,6 @@
     <fraction name="key_bottom_gap">6.250%p</fraction>
     <fraction name="key_horizontal_gap">1.352%p</fraction>
 
-    <dimen name="keyboardHeight_stone">1.317in</dimen>
     <fraction name="keyboard_top_padding_stone">1.556%p</fraction>
     <fraction name="keyboard_bottom_padding_stone">0.778%p</fraction>
     <fraction name="key_bottom_gap_stone">7.506%p</fraction>
@@ -48,16 +48,14 @@
     <fraction name="keyboard_bottom_padding_ics">4.669%p</fraction>
     <fraction name="key_bottom_gap_ics">6.127%p</fraction>
     <fraction name="key_horizontal_gap_ics">1.739%p</fraction>
-    <dimen name="mini_keyboard_horizontal_edges_padding_ics">4dip</dimen>
+    <dimen name="more_keys_keyboard_horizontal_edges_padding_ics">4dp</dimen>
 
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">0.396in</dimen>
+    <dimen name="more_keys_keyboard_slide_allowance">63.36dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-0.330in</dimen>
-    <!-- We use "inch", not "dip" because this value tries dealing with physical distance related
-         to user's finger. -->
-    <dimen name="keyboard_vertical_correction">0.0in</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-52.8dp</dimen>
+    <dimen name="keyboard_vertical_correction">0.0dp</dimen>
 
     <fraction name="key_letter_ratio">55%</fraction>
     <fraction name="key_large_letter_ratio">65%</fraction>
@@ -66,33 +64,33 @@
     <fraction name="key_hint_label_ratio">44%</fraction>
     <fraction name="key_uppercase_letter_ratio">35%</fraction>
     <fraction name="key_preview_text_ratio">82%</fraction>
-    <dimen name="key_preview_height">80sp</dimen>
-    <dimen name="key_preview_offset">0.1in</dimen>
+    <fraction name="spacebar_text_ratio">33.735%</fraction>
+    <dimen name="key_preview_height">80dp</dimen>
+    <dimen name="key_preview_offset">16.0dp</dimen>
 
-    <dimen name="key_label_horizontal_padding">4dip</dimen>
+    <dimen name="key_label_horizontal_padding">4dp</dimen>
     <dimen name="key_hint_letter_padding">1dp</dimen>
     <dimen name="key_popup_hint_letter_padding">2dp</dimen>
     <dimen name="key_uppercase_letter_padding">2dp</dimen>
 
-    <dimen name="key_preview_height_ics">80sp</dimen>
-    <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <dimen name="key_preview_offset_ics">8.0dp</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-26.4dp</dimen>
 
-    <dimen name="suggestions_strip_height">40dip</dimen>
-    <dimen name="more_suggestions_key_horizontal_padding">12dip</dimen>
-    <dimen name="more_suggestions_row_height">40dip</dimen>
-    <dimen name="more_suggestions_bottom_gap">6dip</dimen>
-    <dimen name="more_suggestions_modal_tolerance">0.2in</dimen>
-    <dimen name="more_suggestions_slide_allowance">0.1in</dimen>
+    <dimen name="suggestions_strip_height">40dp</dimen>
+    <dimen name="more_suggestions_key_horizontal_padding">12dp</dimen>
+    <dimen name="more_suggestions_row_height">40dp</dimen>
+    <dimen name="more_suggestions_bottom_gap">6dp</dimen>
+    <dimen name="more_suggestions_modal_tolerance">32.0dp</dimen>
+    <dimen name="more_suggestions_slide_allowance">16.0dp</dimen>
     <integer name="max_more_suggestions_row">6</integer>
     <fraction name="min_more_suggestions_width">90%</fraction>
     <fraction name="more_suggestions_info_ratio">18%</fraction>
-    <dimen name="suggestions_strip_padding">0dip</dimen>
-    <dimen name="suggestion_min_width">44dip</dimen>
-    <dimen name="suggestion_padding">6dip</dimen>
-    <dimen name="suggestion_text_size">18dip</dimen>
-    <dimen name="more_suggestions_hint_text_size">27dip</dimen>
+    <dimen name="suggestions_strip_padding">0dp</dimen>
+    <dimen name="suggestion_min_width">44dp</dimen>
+    <dimen name="suggestion_padding">6dp</dimen>
+    <dimen name="suggestion_text_size">18dp</dimen>
+    <dimen name="more_suggestions_hint_text_size">27dp</dimen>
     <integer name="suggestions_count_in_strip">3</integer>
     <integer name="center_suggestion_percentile">36</integer>
-
-    <dimen name="key_hysteresis_distance">0.05in</dimen>
 </resources>
diff --git a/java/res/values-de-rZZ/donottranslate-more-keys.xml b/java/res/values/donottranslate-config.xml
similarity index 74%
copy from java/res/values-de-rZZ/donottranslate-more-keys.xml
copy to java/res/values/donottranslate-config.xml
index e7ec5e1..ba1cecb 100644
--- a/java/res/values-de-rZZ/donottranslate-more-keys.xml
+++ b/java/res/values/donottranslate-config.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,7 +17,8 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_y">6</string>
-    <string name="more_keys_for_z"></string>
+
+<resources>
+    <bool name="config_require_umlaut_processing">false</bool>
+    <bool name="config_require_ligatures_processing">false</bool>
 </resources>
diff --git a/java/res/values/donottranslate-more-keys.xml b/java/res/values/donottranslate-more-keys.xml
index 6c77539..9b27805 100644
--- a/java/res/values/donottranslate-more-keys.xml
+++ b/java/res/values/donottranslate-more-keys.xml
@@ -19,43 +19,61 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="more_keys_for_a"></string>
-    <string name="more_keys_for_e">3</string>
-    <string name="more_keys_for_i">8</string>
-    <string name="more_keys_for_o">9</string>
-    <string name="more_keys_for_u">7</string>
+    <string name="more_keys_for_e"></string>
+    <string name="more_keys_for_i"></string>
+    <string name="more_keys_for_o"></string>
+    <string name="more_keys_for_u"></string>
     <string name="more_keys_for_s"></string>
     <string name="more_keys_for_n"></string>
     <string name="more_keys_for_c"></string>
-    <string name="more_keys_for_y">6</string>
-    <string name="more_keys_for_q">1</string>
-    <string name="more_keys_for_w">2</string>
+    <string name="more_keys_for_y"></string>
     <string name="more_keys_for_d"></string>
-    <string name="more_keys_for_r">4</string>
-    <string name="more_keys_for_t">5</string>
+    <string name="more_keys_for_r"></string>
+    <string name="more_keys_for_t"></string>
     <string name="more_keys_for_z"></string>
     <string name="more_keys_for_k"></string>
     <string name="more_keys_for_l"></string>
     <string name="more_keys_for_g"></string>
-    <string name="more_keys_for_p">0</string>
     <string name="more_keys_for_v"></string>
-    <string name="keylabel_for_scandinavia_row2_10"></string>
-    <string name="keylabel_for_scandinavia_row2_11"></string>
-    <string name="more_keys_for_scandinavia_row2_10"></string>
-    <string name="more_keys_for_scandinavia_row2_11"></string>
-    <string name="more_keys_for_cyrillic_e"></string>
-    <string name="more_keys_for_cyrillic_soft_sign"></string>
+    <string name="more_keys_for_h"></string>
+    <string name="more_keys_for_j"></string>
+    <string name="more_keys_for_w"></string>
+    <string name="keylabel_for_nordic_row1_11"></string>
+    <string name="keylabel_for_nordic_row2_10"></string>
+    <string name="keylabel_for_nordic_row2_11"></string>
+    <string name="more_keys_for_nordic_row2_10"></string>
+    <string name="more_keys_for_nordic_row2_11"></string>
+    <string name="keylabel_for_east_slavic_row1_9"></string>
+    <string name="keylabel_for_east_slavic_row2_1"></string>
+    <string name="keylabel_for_east_slavic_row3_5"></string>
+    <string name="more_keys_for_cyrillic_u"></string>
+    <string name="more_keys_for_cyrillic_ye"></string>
+    <string name="more_keys_for_cyrillic_en"></string>
     <string name="more_keys_for_cyrillic_ha"></string>
-    <string name="more_keys_for_currency_dollar">¢,£,€,¥,₱</string>
-    <string name="more_keys_for_currency_euro">¢,£,$,¥,₱</string>
-    <string name="more_keys_for_currency_pound">¢,$,€,¥,₱</string>
-    <string name="more_keys_for_currency_general">¢,$,€,£,¥,₱</string>
-    <string name="more_keys_for_smiley">":-)|:-) ,:-(|:-( ,;-)|;-) ,:-P|:-P ,=-O|=-O ,:-*|:-* ,:O|:O ,B-)|B-) ,:-$|:-$ ,:-!|:-! ,:-[|:-[ ,O:-)|O:-) ,:-\\\\\\\\|:-\\\\\\\\ ,:\'(|:\'( ,:-D|:-D "</string>
-    <string name="more_keys_for_punctuation">"\\,,\?,!,:,-,\',\",(,),/,;,+,&amp;,\@"</string>
-    <integer name="mini_keyboard_column_for_punctuation">7</integer>
+    <string name="more_keys_for_east_slavic_row2_1"></string>
+    <string name="more_keys_for_cyrillic_o"></string>
+    <string name="more_keys_for_cyrillic_soft_sign"></string>
+    <string name="keylabel_for_south_slavic_row1_6"></string>
+    <string name="keylabel_for_south_slavic_row2_11"></string>
+    <string name="keylabel_for_south_slavic_row3_1"></string>
+    <string name="keylabel_for_south_slavic_row3_8"></string>
+    <string name="more_keys_for_cyrillic_ie"></string>
+    <string name="more_keys_for_cyrillic_i"></string>
+    <!-- U+00A2: "¢" CENT SIGN
+         U+00A3: "£" POUND SIGN
+         U+20AC: "€" EURO SIGN
+         U+00A5: "¥" YEN SIGN
+         U+20B1: "₱" PESO SIGN -->
+    <string name="more_keys_for_currency_dollar">&#x00A2;,&#x00A3;,&#x20AC;,&#x00A5;,&#x20B1;</string>
+    <string name="more_keys_for_currency_euro">&#x00A2;,&#x00A3;,$,&#x00A5;,&#x20B1;</string>
+    <string name="more_keys_for_currency_pound">&#x00A2;,$,&#x20AC;,&#x00A5;,&#x20B1;</string>
+    <string name="more_keys_for_currency_general">&#x00A2;,$,&#x20AC;,&#x00A3;,&#x00A5;,&#x20B1;</string>
+    <string name="more_keys_for_smiley">"!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ "</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,\\,,\?,\@,&amp;,\\%,+,;,/,(,)"</string>
     <string name="keyhintlabel_for_punctuation"></string>
     <string name="keylabel_for_popular_domain">".com"</string>
     <!-- popular web domains for the locale - most popular, displayed on the keyboard -->
-    <string name="more_keys_for_popular_domain">".net,.org,.gov,.edu"</string>
+    <string name="more_keys_for_popular_domain">"!hasLabels!,.net,.org,.gov,.edu"</string>
     <string name="keylabel_for_symbols_1">1</string>
     <string name="keylabel_for_symbols_2">2</string>
     <string name="keylabel_for_symbols_3">3</string>
@@ -66,39 +84,120 @@
     <string name="keylabel_for_symbols_8">8</string>
     <string name="keylabel_for_symbols_9">9</string>
     <string name="keylabel_for_symbols_0">0</string>
-    <string name="more_keys_for_symbols_1">¹,½,⅓,¼,⅛</string>
-    <string name="more_keys_for_symbols_2">²,⅔</string>
-    <string name="more_keys_for_symbols_3">³,¾,⅜</string>
-    <string name="more_keys_for_symbols_4">⁴</string>
-    <string name="more_keys_for_symbols_5">⅝</string>
+    <string name="additional_more_keys_for_symbols_1"></string>
+    <string name="additional_more_keys_for_symbols_2"></string>
+    <string name="additional_more_keys_for_symbols_3"></string>
+    <string name="additional_more_keys_for_symbols_4"></string>
+    <string name="additional_more_keys_for_symbols_5"></string>
+    <string name="additional_more_keys_for_symbols_6"></string>
+    <string name="additional_more_keys_for_symbols_7"></string>
+    <string name="additional_more_keys_for_symbols_8"></string>
+    <string name="additional_more_keys_for_symbols_9"></string>
+    <string name="additional_more_keys_for_symbols_0"></string>
+    <!-- U+00B9: "¹" SUPERSCRIPT ONE
+         U+00BD: "½" VULGAR FRACTION ONE HALF
+         U+2153: "⅓" VULGAR FRACTION ONE THIRD
+         U+00BC: "¼" VULGAR FRACTION ONE QUARTER
+         U+215B: "⅛" VULGAR FRACTION ONE EIGHTH -->
+    <string name="more_keys_for_symbols_1">&#x00B9;,&#x00BD;,&#x2153;,&#x00BC;,&#x215B;</string>
+    <!-- U+00B2: "²" SUPERSCRIPT TWO
+         U+2154: "⅔" VULGAR FRACTION TWO THIRDS -->
+    <string name="more_keys_for_symbols_2">&#x00B2;,&#x2154;</string>
+    <!-- U+00B3: "³" SUPERSCRIPT THREE
+         U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
+         U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS -->
+    <string name="more_keys_for_symbols_3">&#x00B3;,&#x00BE;,&#x215C;</string>
+    <!-- U+2074: "⁴" SUPERSCRIPT FOUR -->
+    <string name="more_keys_for_symbols_4">&#x2074;</string>
+    <!-- U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS -->
+    <string name="more_keys_for_symbols_5">&#x215D;</string>
     <string name="more_keys_for_symbols_6"></string>
-    <string name="more_keys_for_symbols_7">⅞</string>
+    <!-- U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS -->
+    <string name="more_keys_for_symbols_7">&#x215E;</string>
     <string name="more_keys_for_symbols_8"></string>
     <string name="more_keys_for_symbols_9"></string>
-    <string name="more_keys_for_symbols_0">ⁿ,∅</string>
+    <!-- U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
+         U+2205: "∅" EMPTY SET -->
+    <string name="more_keys_for_symbols_0">&#x207F;,&#x2205;</string>
+    <string name="more_keys_for_am_pm">!fixedColumnOrder!2,!hasLabels!,\@string/label_time_am,\@string/label_time_pm</string>
+    <string name="settings_as_more_key">\@icon/settingsKey|\@integer/key_settings</string>
     <string name="keylabel_for_comma">,</string>
-    <string name="keylabel_for_f1">,</string>
+    <string name="more_keys_for_comma"></string>
+    <string name="action_next_as_more_key">!hasLabels!,\@string/label_next_key|\@integer/key_action_next</string>
+    <string name="action_previous_as_more_key">!hasLabels!,\@string/label_previous_key|\@integer/key_action_previous</string>
     <string name="keylabel_for_symbols_question">\?</string>
     <string name="keylabel_for_symbols_semicolon">;</string>
     <string name="keylabel_for_symbols_percent">%</string>
-    <string name="more_keys_for_comma"></string>
-    <string name="more_keys_for_f1"></string>
-    <!-- @icon/3 is iconSettingsKey -->
-    <string name="more_keys_for_f1_settings">\@icon/3|\@integer/key_settings</string>
-    <!-- @icon/7 is iconTabKey -->
-    <string name="more_keys_for_f1_navigate">\@icon/7|\@integer/key_tab</string>
-    <string name="more_keys_for_symbols_question">¿</string>
+    <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+    <string name="more_keys_for_symbols_question">&#x00BF;</string>
     <string name="more_keys_for_symbols_semicolon"></string>
-    <string name="more_keys_for_symbols_percent">‰</string>
+    <!-- U+2030: "‰" PER MILLE SIGN -->
+    <string name="more_keys_for_symbols_percent">&#x2030;</string>
+    <string name="keylabel_for_tablet_comma">,</string>
+    <string name="keyhintlabel_for_tablet_comma">!</string>
+    <string name="more_keys_for_tablet_comma">!</string>
+    <string name="keyhintlabel_for_tablet_period">\?</string>
+    <string name="more_keys_for_tablet_period">\?</string>
     <string name="keylabel_for_apostrophe">\'</string>
     <string name="keylabel_for_dash">-</string>
     <string name="keyhintlabel_for_apostrophe">\"</string>
     <string name="keyhintlabel_for_dash">_</string>
     <string name="more_keys_for_apostrophe">\"</string>
     <string name="more_keys_for_dash">_</string>
-    <string name="more_keys_for_bullet">♪,♥,♠,♦,♣</string>
-    <string name="more_keys_for_star">†,‡,★</string>
-    <string name="more_keys_for_plus">±</string>
-    <string name="more_keys_for_left_parenthesis">[,{,&lt;</string>
-    <string name="more_keys_for_right_parenthesis">],},&gt;</string>
+    <!-- U+266A: "♪" EIGHTH NOTE
+         U+2665: "♥" BLACK HEART SUIT
+         U+2660: "♠" BLACK SPADE SUIT
+         U+2666: "♦" BLACK DIAMOND SUIT
+         U+2663: "♣" BLACK CLUB SUIT -->
+    <string name="more_keys_for_bullet">&#x266A;,&#x2665;,&#x2660;,&#x2666;,&#x2663;</string>
+    <!-- U+2020: "†" DAGGER
+         U+2021: "‡" DOUBLE DAGGER
+         U+2605: "★" BLACK STAR -->
+    <string name="more_keys_for_star">&#x2020;,&#x2021;,&#x2605;</string>
+    <!-- U+00B1: "±" PLUS-MINUS SIGN -->
+    <string name="more_keys_for_plus">&#x00B1;</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- U+0028: "(" LEFT PARENTHESIS -->
+    <integer name="keycode_for_left_parenthesis">0x0028</integer>
+    <!-- U+0029: ")" RIGHT PARENTHESIS -->
+    <integer name="keycode_for_right_parenthesis">0x0029</integer>
+    <string name="more_keys_for_left_parenthesis">!fixedColumnOrder!3,&lt;,{,[</string>
+    <string name="more_keys_for_right_parenthesis">!fixedColumnOrder!3,&gt;,},]</string>
+    <!-- U+003C: "<" LESS-THAN SIGN -->
+    <integer name="keycode_for_less_than">0x003C</integer>
+    <!-- U+003E: ">" GREATER-THAN SIGN -->
+    <integer name="keycode_for_greater_than">0x003E</integer>
+    <!-- U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+         U+2264: "≤" LESS-THAN OR EQUAL TO
+         U+2265: "≥" GREATER-THAN EQUAL TO
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+         The following characters don't need BIDI mirroring.
+         U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;,&#x2264;,&#x00AB;</string>
+    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;,&#x2265;,&#x00BB;</string>
+    <!-- U+005B: "[" LEFT SQUARE BRACKET -->
+    <integer name="keycode_for_left_square_bracket">0x005B</integer>
+    <!-- U+005D: "]" RIGHT SQUARE BRACKET -->
+    <integer name="keycode_for_right_square_bracket">0x005D</integer>
+    <!-- U+007B: "{" LEFT CURLY BRACKET -->
+    <integer name="keycode_for_left_curly_bracket">0x007B</integer>
+    <!-- U+007D: "}" RIGHT CURLY BRACKET -->
+    <integer name="keycode_for_right_curly_bracket">0x007D</integer>
+    <string name="more_keys_for_single_quote">!fixedColumnOrder!4,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+    <!-- <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
 </resources>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index aefaec9..68a8cab 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -19,32 +19,30 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Symbols that are suggested between words -->
-    <string name="suggested_punctuations">!?,:;\u0022()\u0027-/@_</string>
-    <!-- Symbols that should be swapped with a magic space -->
-    <string name="magic_space_swapping_symbols">.,;:!?)]}\u0022</string>
-    <!-- Symbols that should strip a magic space -->
-    <string name="magic_space_stripping_symbols">\u0009\u0020\n/_\u0027-</string>
-    <!-- Symbols that should convert magic spaces into real space -->
-    <string name="magic_space_promoting_symbols">([*&amp;@{&lt;&gt;+=|</string>
+    <string name="suggested_punctuations">!,?,\\,,:,;,\",(,),\',-,/,@,_</string>
+    <!-- Symbols that should be swapped with a weak space -->
+    <string name="weak_space_swapping_symbols">.,;:!?)]}\"</string>
+    <!-- Symbols that should strip a weak space -->
+    <string name="weak_space_stripping_symbols">"&#x0009;&#x0020;\n/_\'-"</string>
+    <!-- Symbols that should convert weak spaces into real space -->
+    <string name="phantom_space_promoting_symbols">([*&amp;@{&lt;&gt;+=|</string>
     <!-- Symbols that do NOT separate words -->
-    <string name="symbols_excluded_from_word_separators">\u0027-</string>
+    <string name="symbols_excluded_from_word_separators">\'-</string>
     <!-- Word separator list is the union of all symbols except those that are not separators:
-    magic_space_swapping_symbols | magic_space_stripping_symbols |
-            magic_space_neutral_symbols \ symbols_excluded_from_word_separators -->
+    weak_space_swapping_symbols | weak_space_stripping_symbols
+            \ symbols_excluded_from_word_separators -->
     <!-- Symbol characters list that should switch back to the main layout -->
-    <!--  \u0022: Quotation mark (double quotation mark)
-          \u0027: Apostrophe (single quotation mark)
-          \u2018: Left single quotation mark
-          \u2019: Right single quotation mark
-          \u201a: Single low-9 quotation mark
-          \u201b: Single high-reversed-9 quotation mark
-          \u201c: Left double quotation mark
-          \u201d: Right double quotation mark
-          \u201e: Double low-9 quotation mark
-          \u201f: Double high-reversed-9 quotation mark
-          \u00ab: Left-pointing double angle quotation mark
-          \u00bb: Right-pointing double angle quotation mark  -->
-    <!-- string name="layout_switch_back_symbols">\u0022\u0027\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u201f\u00ab\u00bb</string> -->
+    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+    <!-- <string name="layout_switch_back_symbols">\"\'&#x2018;&#x2019;&#x201A;&#x201B;&#x201C;&#x201D;&#x201E;&#x201F;&#x00AB;&#x00BB;</string> -->
     <string name="layout_switch_back_symbols"></string>
 
     <!-- Label for "switch to more symbol" modifier key.  Must be short to fit on key! -->
@@ -57,7 +55,13 @@
     <!-- Label for "switch to phone numeric" key.  Must be short to fit on key! -->
     <string name="label_to_phone_numeric_key">123</string>
     <!-- Label for "switch to phone symbols" key.  Must be short to fit on key! -->
-    <string name="label_to_phone_symbols_key">\uff0a\uff03</string>
+    <!-- U+FF0A: "*" FULLWIDTH ASTERISK
+         U+FF03: "#" FULLWIDTH NUMBER SIGN -->
+    <string name="label_to_phone_symbols_key">&#xFF0A;&#xFF03;</string>
+    <!-- Key label for "ante meridiem" -->
+    <string name="label_time_am">"AM"</string>
+    <!-- Key label for "post meridiem" -->
+    <string name="label_time_pm">"PM"</string>
 
     <!--  Always show the suggestion strip -->
     <string name="prefs_suggestion_visibility_show_value">0</string>
@@ -120,6 +124,7 @@
     <!-- Title for Latin keyboard debug settings activity / dialog -->
     <string name="english_ime_debug_settings">Android keyboard Debug settings</string>
     <string name="prefs_debug_mode">Debug Mode</string>
+    <string name="prefs_force_non_distinct_multitouch">Force non-distinct multitouch</string>
 
     <!-- Keyboard theme names -->
     <string name="layout_basic">Basic</string>
@@ -151,19 +156,26 @@
     <string-array name="subtype_locale_exception_keys">
         <item>en_US</item>
         <item>en_GB</item>
-        <item>de_ZZ</item>
+        <item>de_QY</item>
+        <item>zz_QY</item>
     </string-array>
     <string-array name="subtype_locale_exception_values">
         <item>English (US)</item>
         <item>English (UK)</item>
-        <item>Deutsch (QWERTY)</item>
+        <item>@string/subtype_generic_qwerty</item>
+        <item>@string/subtype_qwerty</item>
     </string-array>
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
+    <!-- Description for generic QWERTY keyboard subtype -->
+    <string name="subtype_generic_qwerty">%s (QWERTY)</string>
+    <!-- Description for language agnostic QWERTY keyboard subtype -->
+    <string name="subtype_qwerty">QWERTY</string>
 
     <!-- dictionary pack package name /settings activity (for shared prefs and settings) -->
     <string name="dictionary_pack_package_name">com.google.android.inputmethod.latin.dictionarypack</string>
     <string name="dictionary_pack_settings_activity">com.google.android.inputmethod.latin.dictionarypack.DictionarySettingsActivity</string>
     <string name="settings_ms">ms</string>
+    <string name="settings_warning_researcher_mode">Attention!  You are using the special keyboard for research purposes.</string>
 </resources>
diff --git a/java/res/values/keyboard-heights.xml b/java/res/values/keyboard-heights.xml
new file mode 100644
index 0000000..7d5b583
--- /dev/null
+++ b/java/res/values/keyboard-heights.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 1.285in -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Droid -->
+        <item>sholes,227.0167</item>
+        <!-- Nexus One -->
+        <item>mahimahi,217.5932</item>
+        <!-- Nexus S -->
+        <item>herring,200.8554</item>
+        <!-- Galaxy Nexus -->
+        <item>tuna,202.5869</item>
+    </string-array>
+</resources>
diff --git a/java/res/values/keyboard-icons-black.xml b/java/res/values/keyboard-icons-black.xml
index f767cb3..1ff597a 100644
--- a/java/res/values/keyboard-icons-black.xml
+++ b/java/res/values/keyboard-icons-black.xml
@@ -30,10 +30,14 @@
         <item name="iconTabKey">@drawable/sym_bkeyboard_tab</item>
         <item name="iconShortcutKey">@drawable/sym_bkeyboard_mic</item>
         <item name="iconShortcutForLabel">@drawable/sym_bkeyboard_label_mic</item>
-        <item name="iconShiftedShiftKey">@drawable/sym_bkeyboard_shift_locked</item>
+        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_bkeyboard_space</item>
+        <item name="iconShiftKeyShifted">@drawable/sym_bkeyboard_shift_locked</item>
+        <item name="iconDisabledShortcutKey">@drawable/sym_bkeyboard_voice_off</item>
         <item name="iconPreviewTabKey">@drawable/sym_keyboard_feedback_tab</item>
-        <!-- LatinKeyboard icons -->
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="disabledShortcutIcon">@drawable/sym_bkeyboard_voice_off</item>
+        <!-- TODO: Needs dedicated black theme globe icon -->
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
+        <!-- TODO: Needs dedicated black theme ZWNJ and ZWJ icons -->
+        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo</item>
+        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-ics.xml b/java/res/values/keyboard-icons-ics.xml
index f102143..0774d57 100644
--- a/java/res/values/keyboard-icons-ics.xml
+++ b/java/res/values/keyboard-icons-ics.xml
@@ -23,16 +23,18 @@
         <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo</item>
         <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo</item>
         <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo</item>
-        <item name="iconSpaceKey">@drawable/sym_keyboard_space_holo</item>
+        <item name="iconSpaceKey">@null</item>
         <item name="iconReturnKey">@drawable/sym_keyboard_return_holo</item>
         <item name="iconSearchKey">@drawable/sym_keyboard_search_holo</item>
         <item name="iconTabKey">@drawable/sym_keyboard_tab_holo</item>
         <item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo</item>
         <item name="iconShortcutForLabel">@drawable/sym_keyboard_label_mic_holo</item>
-        <item name="iconShiftedShiftKey">@drawable/sym_keyboard_shift_locked_holo</item>
+        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo</item>
+        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo</item>
+        <item name="iconDisabledShortcutKey">@drawable/sym_keyboard_voice_off_holo</item>
         <item name="iconPreviewTabKey">@drawable/sym_keyboard_feedback_tab</item>
-        <!-- LatinKeyboard icons -->
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
-        <item name="disabledShortcutIcon">@drawable/sym_keyboard_voice_off_holo</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
+        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo</item>
+        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-white.xml b/java/res/values/keyboard-icons-white.xml
index 07ece66..5798786 100644
--- a/java/res/values/keyboard-icons-white.xml
+++ b/java/res/values/keyboard-icons-white.xml
@@ -26,10 +26,14 @@
         <item name="iconTabKey">@drawable/sym_keyboard_tab</item>
         <item name="iconShortcutKey">@drawable/sym_keyboard_mic</item>
         <item name="iconShortcutForLabel">@drawable/sym_keyboard_label_mic</item>
-        <item name="iconShiftedShiftKey">@drawable/sym_keyboard_shift_locked</item>
+        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space</item>
+        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked</item>
+        <!-- TODO: Needs non-holo disabled shortcut icon drawable -->
+        <item name="iconDisabledShortcutKey">@drawable/sym_keyboard_voice_off_holo</item>
         <item name="iconPreviewTabKey">@drawable/sym_keyboard_feedback_tab</item>
-        <!-- LatinKeyboard icons -->
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="disabledShortcutIcon">@drawable/sym_keyboard_voice_off_holo</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
+        <!-- TODO: Needs dedicated black theme ZWNJ and ZWJ icons -->
+        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo</item>
+        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
     </style>
 </resources>
diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index 59cc075..d3d9b63 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -21,11 +21,17 @@
 <resources>
     <!-- These code should be aligned with Keyboard.CODE_*. -->
     <integer name="key_tab">9</integer>
-    <integer name="key_return">10</integer>
+    <integer name="key_enter">10</integer>
     <integer name="key_space">32</integer>
     <integer name="key_shift">-1</integer>
     <integer name="key_switch_alpha_symbol">-2</integer>
-    <integer name="key_delete">-5</integer>
-    <integer name="key_settings">-6</integer>
-    <integer name="key_shortcut">-7</integer>
+    <integer name="key_output_text">-3</integer>
+    <integer name="key_delete">-4</integer>
+    <integer name="key_settings">-5</integer>
+    <integer name="key_shortcut">-6</integer>
+    <integer name="key_action_enter">-7</integer>
+    <integer name="key_action_next">-8</integer>
+    <integer name="key_action_previous">-9</integer>
+    <integer name="key_language_switch">-10</integer>
+    <integer name="key_unspecified">-11</integer>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index e00547a..4f038e1 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -20,22 +20,26 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Title for Latin keyboard  -->
     <string name="english_ime_name">Android keyboard</string>
+    <!-- Application name for opensource Android keyboard. AOSP(Android Open Source Project) should not be translated. -->
+    <string name="aosp_android_keyboard_ime_name">Android keyboard (AOSP)</string>
     <!-- Title for Latin keyboard settings activity / dialog -->
     <string name="english_ime_settings">Android keyboard settings</string>
     <!-- Title for Latin keyboard input options dialog [CHAR LIMIT=25] -->
     <string name="english_ime_input_options">Input options</string>
 
-    <!-- Title for Latin Keyboard spell checker service -->
-    <string name="spell_checker_service_name">Android correction</string>
+    <!-- Name of Android spell checker service -->
+    <string name="spell_checker_service_name">Android spell checker</string>
+    <!-- Name of Android spell checker service. AOSP(Android Open Source Project) should not be translated. -->
+    <string name="aosp_spell_checker_service_name">Android spell checker (AOSP)</string>
 
     <!-- Title for the spell checking service settings screen -->
     <string name="android_spell_checker_settings">Spell checking settings</string>
 
-    <!-- Title for the "use proximity" option for spell checking [CHAR LIMIT=25] -->
-    <string name="use_proximity_option_title">Use proximity data</string>
+    <!-- Title for the spell checker option to turn on/off contact names lookup [CHAR LIMIT=25] -->
+    <string name="use_contacts_for_spellchecking_option_title">Look up contact names</string>
 
-    <!-- Description for the "use proximity" option for spell checking [CHAR LIMIT=65] -->
-    <string name="use_proximity_option_summary">Use a keyboard-like proximity algorithm for spell checking</string>
+    <!-- Description for the spell checker option to turn on/off contact names lookup. [CHAR LIMIT=65] -->
+    <string name="use_contacts_for_spellchecking_option_summary">Spell checker uses entries from your contact list</string>
 
     <!-- Option to provide vibrate/haptic feedback on keypress -->
     <string name="vibrate_on_keypress">Vibrate on keypress</string>
@@ -58,7 +62,14 @@
     <!-- Option name for advanced settings screen [CHAR LIMIT=25] -->
     <string name="advanced_settings">Advanced settings</string>
     <!-- Option summary for advanced settings screen [CHAR LIMIT=65 (two lines) or 30 (fits on one line, preferable)] -->
-    <string name="advanced_settings_summary">Options for expert users</string>
+    <string name="advanced_settings_summary">Options for experts</string>
+
+    <!-- Option name for including other IMEs in the language switch list [CHAR LIMIT=25] -->
+    <string name="include_other_imes_in_language_switch_list">Switch to other input methods</string>
+    <!-- Option summary for including other IMEs in the language switch list [CHAR LIMIT=65] -->
+    <string name="include_other_imes_in_language_switch_list_summary">Language switch key covers other input methods too</string>
+    <!-- Option to suppress language switch key [CHAR LIMIT=25] -->
+    <string name="suppress_language_switch_key">Suppress language switch key</string>
 
     <!-- Option for the dismiss delay of the key popup [CHAR LIMIT=25] -->
     <string name="key_preview_popup_dismiss_delay">Key popup dismiss delay</string>
@@ -93,9 +104,6 @@
     <string name="prefs_suggestion_visibility_show_only_portrait_name">Show on portrait mode</string>
     <string name="prefs_suggestion_visibility_hide_name">Always hide</string>
 
-    <!-- Option to show/hide the settings key -->
-    <string name="prefs_settings_key">Show settings key</string>
-
     <!-- Option to decide the auto correction threshold score -->
     <!-- Option to enable auto correction [CHAR LIMIT=20]-->
     <string name="auto_correction">Auto correction</string>
@@ -126,6 +134,8 @@
     <string name="label_go_key">Go</string>
     <!-- Label for soft enter key when it performs NEXT action.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_next_key">Next</string>
+    <!-- Label for soft enter key when it performs PREVIOUS action.  Must be short to fit on key! [CHAR LIMIT=5] -->
+    <string name="label_previous_key">Prev</string>
     <!-- Label for soft enter key when it performs DONE action.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_done_key">Done</string>
     <!-- Label for soft enter key when it performs SEND action.  Must be short to fit on key! [CHAR LIMIT=5] -->
@@ -152,12 +162,12 @@
 
     <!-- Spoken description for unknown keyboard keys. -->
     <string name="spoken_description_unknown">Key code %d</string>
-    <!-- Spoken description for the "Shift" keyboard key. -->
+    <!-- Spoken description for the "Shift" keyboard key when "Shift" is off. -->
     <string name="spoken_description_shift">Shift</string>
-    <!-- Spoken description for the "Shift" keyboard key's pressed state. -->
-    <string name="spoken_description_shift_shifted">Shift enabled</string>
-    <!-- Spoken description for the "Shift" keyboard key's pressed state. -->
-    <string name="spoken_description_caps_lock">Caps lock enabled</string>
+    <!-- Spoken description for the "Shift" keyboard key when "Shift" is on. -->
+    <string name="spoken_description_shift_shifted">Shift on (tap to disable)</string>
+    <!-- Spoken description for the "Shift" keyboard key when "Caps lock" is on. -->
+    <string name="spoken_description_caps_lock">Caps lock on (tap to disable)</string>
     <!-- Spoken description for the "Delete" keyboard key. -->
     <string name="spoken_description_delete">Delete</string>
     <!-- Spoken description for the "To Symbol" keyboard key. -->
@@ -178,116 +188,24 @@
     <string name="spoken_description_smiley">Smiley face</string>
     <!-- Spoken description for the "Return" keyboard key. -->
     <string name="spoken_description_return">Return</string>
-
-    <!-- Spoken description for the "," keyboard key. -->
-    <string name="spoken_description_comma">Comma</string>
-    <!-- Spoken description for the "." keyboard key. -->
-    <string name="spoken_description_period">Period</string>
-    <!-- Spoken description for the "(" keyboard key. -->
-    <string name="spoken_description_left_parenthesis">Left parenthesis</string>
-    <!-- Spoken description for the ")" keyboard key. -->
-    <string name="spoken_description_right_parenthesis">Right parenthesis</string>
-    <!-- Spoken description for the ":" keyboard key. -->
-    <string name="spoken_description_colon">Colon</string>
-    <!-- Spoken description for the ";" keyboard key. -->
-    <string name="spoken_description_semicolon">Semicolon</string>
-    <!-- Spoken description for the "!" keyboard key. -->
-    <string name="spoken_description_exclamation_mark">Exclamation mark</string>
-    <!-- Spoken description for the "?" keyboard key. -->
-    <string name="spoken_description_question_mark">Question mark</string>
-    <!-- Spoken description for the """ keyboard key. -->
-    <string name="spoken_description_double_quote">Double quote</string>
-    <!-- Spoken description for the "'" keyboard key. -->
-    <string name="spoken_description_single_quote">Single quote</string>
-    <!-- Spoken description for the "\u2022" (BULLET) keyboard key. -->
+    <!-- Spoken description for the "U+2022" (BULLET) keyboard key. -->
     <string name="spoken_description_dot">Dot</string>
-    <!-- Spoken description for the "\u221a" (SQUARE ROOT) keyboard key. -->
-    <string name="spoken_description_square_root">Square root</string>
-    <!-- Spoken description for the "\u03C0" (GREEK SMALL LETTER PI) keyboard key. -->
-    <string name="spoken_description_pi">Pi</string>
-    <!-- Spoken description for the "\u0394" (GREEK CAPITAL LETTER DELTA) keyboard key. -->
-    <string name="spoken_description_delta">Delta</string>
-    <!-- Spoken description for the "\u2122" (TRADE MARK SIGN) keyboard key. -->
-    <string name="spoken_description_trademark">Trademark</string>
-    <!-- Spoken description for the "\u2105" (CARE OF) keyboard key. -->
-    <string name="spoken_description_care_of">Care of</string>
-    <!-- Spoken description for the "*" keyboard key. -->
-    <string name="spoken_description_star">Star</string>
-    <!-- Spoken description for the "#" keyboard key. -->
-    <string name="spoken_description_pound">Pound</string>
-    <!-- Spoken description for the "\u2026" (HORIZONTAL ELLIPSIS) keyboard key. -->
-    <string name="spoken_description_ellipsis">Ellipsis</string>
-    <!-- Spoken description for the "\u201E" (DOUBLE LOW-9 QUOTATION MARK) keyboard key. -->
-    <string name="spoken_description_low_double_quote">Low double quote</string>
 
-    <!-- Voice related labels -->
+    <!-- Spoken feedback after turning "Shift" mode on. -->
+    <string name="spoken_description_shiftmode_on">Shift enabled</string>
+    <!-- Spoken feedback after turning "Caps lock" mode on. -->
+    <string name="spoken_description_shiftmode_locked">Caps lock enabled</string>
+    <!-- Spoken feedback after turning "Shift" mode off. -->
+    <string name="spoken_description_shiftmode_off">Shift disabled</string>
 
-    <!-- Title of the warning dialog that shows when a user initiates voice input for
-         the first time. -->
-    <string name="voice_warning_title">Voice input</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">Voice input is not currently supported for your language, but does work in English.</string>
-
-    <!-- Message of the warning dialog that shows when a user initiates voice input for
-         the first time, or turns it on in settings. [CHAR LIMIT=200] -->
-    <string name="voice_warning_may_not_understand">Voice input uses Google\'s speech recognition. <a href="http://m.google.com/privacy">The Mobile Privacy Policy</a> applies.</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. [CHAR LIMIT=200] -->
-    <string name="voice_warning_how_to_turn_off">To turn off voice input, go to input method settings.</string>
-
-    <!-- Message to show when user enables the voice input settings (which says
-        "Press the microphone button"). [CHAR LIMIT=100] -->
-    <string name="voice_hint_dialog_message">To use voice input, press the microphone button.</string>
-
-    <!-- Short message to tell the user the system is ready for them to speak. -->
-    <string name="voice_listening">Speak now</string>
-
-    <!-- Short message shown after the user finishes speaking. -->
-    <string name="voice_working">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">Error. Please try again.</string>
-
-    <!-- Short message shown for a network error. -->
-    <string name="voice_network_error">Couldn\'t connect</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">Error, too much speech.</string>
-
-    <!-- Short message shown for an audio error. -->
-    <string name="voice_audio_error">Audio problem</string>
-
-    <!-- Short message shown for an error with the voice server. -->
-    <string name="voice_server_error">Server error</string>
-
-    <!-- Short message shown when no speech is heard. -->
-    <string name="voice_speech_timeout">No speech heard</string>
-
-    <!-- Short message shown when the server couldn't parse any speech. -->
-    <string name="voice_no_match">No matches found</string>
-
-    <!-- Short message shown when the user initiates voice and voice search is not installed. -->
-    <string name="voice_not_installed">Voice search not installed</string>
-
-    <!-- Short hint shown in candidate view to explain voice input. -->
-    <string name="voice_swipe_hint"><b>Hint:</b> Swipe across keyboard to speak</string>
-
-    <!-- Short hint shown in candidate view to explain that user can speak punctuation. -->
-    <string name="voice_punctuation_hint"><b>Hint:</b> Next time, try speaking punctuation like \"period\", \"comma\", or \"question mark\".</string>
-
-    <!-- Label on button to stop recognition. Must be short to fit on button. -->
-    <string name="cancel">Cancel</string>
-
-    <!-- Label on button when an error occurs -->
-    <string name="ok">OK</string>
+    <!-- Spoken feedback after changing to the symbols keyboard. -->
+    <string name="spoken_description_mode_symbol">Symbols mode</string>
+    <!-- Spoken feedback after changing to the alphanumeric keyboard. -->
+    <string name="spoken_description_mode_alpha">Letters mode</string>
+    <!-- Spoken feedback after changing to the phone dialer keyboard. -->
+    <string name="spoken_description_mode_phone">Phone mode</string>
+    <!-- Spoken feedback after changing to the shifted phone dialer (symbols) keyboard. -->
+    <string name="spoken_description_mode_phone_shift">Phone symbols mode</string>
 
     <!-- Preferences item for enabling speech input -->
     <string name="voice_input">Voice input key</string>
@@ -307,9 +225,6 @@
     <!-- On settings screen, voice input pop-up menu summary text to never show voice key [CHAR LIMIT=20] -->
     <string name="voice_input_modes_summary_off">Voice input is disabled</string>
 
-    <!-- Title of the dialog for selecting input methods. [CHAR LIMIT=20] -->
-    <string name="selectInputMethod">Select input method</string>
-
     <!-- Title for configuring input method settings [CHAR LIMIT=35] -->
     <string name="configure_input_method">Configure input methods</string>
 
@@ -320,7 +235,7 @@
     <string name="select_language">Input languages</string>
 
     <!-- Add to dictionary hint -->
-    <string name="hint_add_to_dictionary">\u2190 Touch again to save</string>
+    <string name="hint_add_to_dictionary">Touch again to save</string>
 
     <!-- Inform the user that a particular language has an available dictionary -->
     <string name="has_dictionary">Dictionary available</string>
@@ -333,8 +248,6 @@
     <!-- Title of the item to change the keyboard theme [CHAR LIMIT=20]-->
     <string name="keyboard_layout">Keyboard theme</string>
 
-    <!-- Description for German QWERTY keyboard subtype [CHAR LIMIT=22] -->
-    <string name="subtype_de_qwerty">German QWERTY</string>
     <!-- Description for English (United Kingdom) keyboard subtype [CHAR LIMIT=22] -->
     <string name="subtype_en_GB">English (UK)</string>
     <!-- Description for English (United States) keyboard subtype [CHAR LIMIT=22] -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 43aa583..b08ff3b 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -17,13 +17,14 @@
 <resources>
     <!-- Theme "Basic" -->
     <style name="Keyboard">
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">0</item>
         <item name="touchPositionCorrectionData">@array/touch_position_correction_data_empty</item>
         <item name="rowHeight">25%p</item>
         <item name="keyboardHeight">@dimen/keyboardHeight</item>
         <item name="maxKeyboardHeight">@fraction/maxKeyboardHeight</item>
         <item name="minKeyboardHeight">@fraction/minKeyboardHeight</item>
-        <item name="moreKeysTemplate">@xml/kbd_mini_keyboard_template</item>
+        <item name="moreKeysTemplate">@xml/kbd_more_keys_keyboard_template</item>
         <item name="keyboardTopPadding">@fraction/keyboard_top_padding</item>
         <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding</item>
         <item name="keyboardHorizontalEdgesPadding">@fraction/keyboard_horizontal_edges_padding</item>
@@ -31,12 +32,6 @@
         <item name="verticalGap">@fraction/key_bottom_gap</item>
         <item name="maxMoreKeysColumn">@integer/config_max_more_keys_column</item>
     </style>
-    <style name="LatinKeyboard">
-        <item name="autoCorrectionSpacebarLedEnabled">@bool/config_auto_correction_spacebar_led_enabled
-        </item>
-        <item name="spacebarTextColor">#FFC0C0C0</item>
-        <item name="spacebarTextShadowColor">#80000000</item>
-    </style>
     <style name="KeyboardView">
         <item name="android:background">@drawable/keyboard_background</item>
         <item name="keyBackground">@drawable/btn_keyboard_key</item>
@@ -45,18 +40,18 @@
         <item name="keyLabelRatio">@fraction/key_label_ratio</item>
         <item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
         <item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
-        <item name="keyUppercaseLetterRatio">@fraction/key_uppercase_letter_ratio</item>
+        <item name="keyShiftedLetterHintRatio">@fraction/key_uppercase_letter_ratio</item>
         <item name="keyTextStyle">normal</item>
         <item name="keyTextColor">#FFFFFFFF</item>
         <item name="keyTextInactivatedColor">#FFFFFFFF</item>
         <item name="keyHintLetterColor">#80000000</item>
         <item name="keyHintLabelColor">#E0E0E4E5</item>
-        <item name="keyUppercaseLetterInactivatedColor">#66E0E4E5</item>
-        <item name="keyUppercaseLetterActivatedColor">#CCE0E4E5</item>
+        <item name="keyShiftedLetterHintInactivatedColor">#66E0E4E5</item>
+        <item name="keyShiftedLetterHintActivatedColor">#CCE0E4E5</item>
         <item name="keyLabelHorizontalPadding">@dimen/key_label_horizontal_padding</item>
         <item name="keyHintLetterPadding">@dimen/key_hint_letter_padding</item>
         <item name="keyPopupHintLetterPadding">@dimen/key_popup_hint_letter_padding</item>
-        <item name="keyUppercaseLetterPadding">@dimen/key_uppercase_letter_padding</item>
+        <item name="keyShiftedLetterHintPadding">@dimen/key_uppercase_letter_padding</item>
         <item name="keyPreviewLayout">@layout/key_preview</item>
         <item name="keyPreviewBackground">@drawable/keyboard_key_feedback</item>
         <item name="keyPreviewLeftBackground">@null</item>
@@ -65,31 +60,57 @@
         <item name="keyPreviewOffset">@dimen/key_preview_offset</item>
         <item name="keyPreviewHeight">@dimen/key_preview_height</item>
         <item name="keyPreviewTextRatio">@fraction/key_preview_text_ratio</item>
-        <item name="moreKeysLayout">@layout/mini_keyboard</item>
+        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
+        <item name="moreKeysLayout">@layout/more_keys_keyboard</item>
         <item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
         <item name="shadowColor">#BB000000</item>
         <item name="shadowRadius">2.75</item>
-        <item name="backgroundDimAmount">0.5</item>
+        <item name="backgroundDimAlpha">128</item>
+        <!-- Common attributes of LatinKeyboardView -->
+        <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
+        <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
+        <item name="touchNoiseThresholdDistance">@dimen/config_touch_noise_threshold_distance</item>
+        <item name="slidingKeyInputEnable">@bool/config_sliding_key_input_enabled</item>
+        <item name="keyRepeatStartTimeout">@integer/config_key_repeat_start_timeout</item>
+        <item name="keyRepeatInterval">@integer/config_key_repeat_interval</item>
+        <item name="longPressKeyTimeout">@integer/config_long_press_key_timeout</item>
+        <item name="longPressShiftKeyTimeout">@integer/config_long_press_shift_key_timeout</item>
+        <item name="longPressSpaceKeyTimeout">@integer/config_long_press_space_key_timeout</item>
+        <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
+        <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
+        <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
+        <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
+        <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
+        <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
     </style>
     <style
-        name="MiniKeyboard"
+        name="LatinKeyboardView"
+        parent="KeyboardView">
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="spacebarTextColor">#FFC0C0C0</item>
+        <item name="spacebarTextShadowColor">#80000000</item>
+    </style>
+    <style
+        name="MoreKeysKeyboard"
         parent="Keyboard"
     >
-        <item name="keyboardTopPadding">0dip</item>
-        <item name="keyboardBottomPadding">0dip</item>
-        <item name="horizontalGap">0dip</item>
+        <item name="keyboardTopPadding">0dp</item>
+        <item name="keyboardBottomPadding">0dp</item>
+        <item name="horizontalGap">0dp</item>
     </style>
     <style
-        name="MiniKeyboardView"
+        name="MoreKeysKeyboardView"
         parent="KeyboardView"
     >
         <item name="keyBackground">@drawable/btn_keyboard_key_popup</item>
-        <item name="verticalCorrection">@dimen/mini_keyboard_vertical_correction</item>
+        <item name="verticalCorrection">@dimen/more_keys_keyboard_vertical_correction</item>
     </style>
-    <style name="MiniKeyboardPanelStyle">
+    <style name="MoreKeysKeyboardPanelStyle">
         <item name="android:background">@drawable/keyboard_popup_panel_background</item>
-        <item name="android:paddingLeft">@dimen/mini_keyboard_horizontal_edges_padding</item>
-        <item name="android:paddingRight">@dimen/mini_keyboard_horizontal_edges_padding</item>
+        <item name="android:paddingLeft">@dimen/more_keys_keyboard_horizontal_edges_padding</item>
+        <item name="android:paddingRight">@dimen/more_keys_keyboard_horizontal_edges_padding</item>
     </style>
     <style name="SuggestionsStripBackgroundStyle">
         <item name="android:background">@drawable/keyboard_suggest_strip</item>
@@ -98,7 +119,8 @@
         name="SuggestionsViewStyle"
         parent="SuggestionsStripBackgroundStyle"
     >
-        <item name="suggestionStripOption">autoCorrectBold</item>
+        <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
+        <item name="colorValidTypedWord">#FFFCAE00</item>
         <item name="colorTypedWord">@android:color/white</item>
         <item name="colorAutoCorrect">#FFFCAE00</item>
         <item name="colorSuggested">#FFFCAE00</item>
@@ -110,7 +132,7 @@
     </style>
     <style
         name="MoreSuggestionsViewStyle"
-        parent="MiniKeyboardView"
+        parent="MoreKeysKeyboardView"
     >
     </style>
     <style name="SuggestionBackgroundStyle">
@@ -124,6 +146,7 @@
         name="Keyboard.HighContrast"
         parent="Keyboard"
     >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">1</item>
     </style>
     <style
@@ -133,26 +156,29 @@
         <item name="android:background">@android:color/black</item>
         <item name="keyBackground">@drawable/btn_keyboard_key3</item>
     </style>
+    <style
+        name="LatinKeyboardView.HighContrast"
+        parent="KeyboardView.HighContrast"
+    >
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="spacebarTextColor">#FFC0C0C0</item>
+        <item name="spacebarTextShadowColor">#80000000</item>
+    </style>
     <!-- Theme "Stone" -->
     <style
         name="Keyboard.Stone"
         parent="Keyboard"
     >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">6</item>
-        <item name="keyboardHeight">@dimen/keyboardHeight_stone</item>
         <item name="keyboardTopPadding">@fraction/keyboard_top_padding_stone</item>
         <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_stone</item>
         <item name="horizontalGap">@fraction/key_horizontal_gap_stone</item>
         <item name="verticalGap">@fraction/key_bottom_gap_stone</item>
     </style>
     <style
-        name="LatinKeyboard.Stone"
-        parent="LatinKeyboard"
-    >
-        <item name="spacebarTextColor">#FF000000</item>
-        <item name="spacebarTextShadowColor">#D0FFFFFF</item>
-    </style>
-    <style
         name="KeyboardView.Stone"
         parent="KeyboardView"
     >
@@ -161,21 +187,31 @@
         <item name="keyTextInactivatedColor">#FF808080</item>
         <item name="keyHintLetterColor">#80000000</item>
         <item name="keyHintLabelColor">#E0000000</item>
-        <item name="keyUppercaseLetterInactivatedColor">#66000000</item>
-        <item name="keyUppercaseLetterActivatedColor">#CC000000</item>
+        <item name="keyShiftedLetterHintInactivatedColor">#66000000</item>
+        <item name="keyShiftedLetterHintActivatedColor">#CC000000</item>
         <item name="shadowColor">#FFFFFFFF</item>
     </style>
     <style
-        name="MiniKeyboard.Stone"
-        parent="Keyboard.Stone"
+        name="LatinKeyboardView.Stone"
+        parent="KeyboardView.Stone"
     >
-        <item name="keyboardTopPadding">0dip</item>
-        <item name="keyboardBottomPadding">0dip</item>
-        <item name="horizontalGap">0dip</item>
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="spacebarTextColor">#FF000000</item>
+        <item name="spacebarTextShadowColor">#D0FFFFFF</item>
     </style>
     <style
-        name="MiniKeyboardView.Stone"
-        parent="MiniKeyboardView"
+        name="MoreKeysKeyboard.Stone"
+        parent="Keyboard.Stone"
+    >
+        <item name="keyboardTopPadding">0dp</item>
+        <item name="keyboardBottomPadding">0dp</item>
+        <item name="horizontalGap">0dp</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardView.Stone"
+        parent="MoreKeysKeyboardView"
     >
         <item name="keyBackground">@drawable/btn_keyboard_key_stone</item>
         <item name="keyTextColor">#FF000000</item>
@@ -186,6 +222,7 @@
         name="Keyboard.Stone.Bold"
         parent="Keyboard.Stone"
     >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">7</item>
     </style>
     <style
@@ -194,11 +231,22 @@
     >
         <item name="keyTextStyle">bold</item>
     </style>
+    <style
+        name="LatinKeyboardView.Stone.Bold"
+        parent="KeyboardView.Stone.Bold"
+    >
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="spacebarTextColor">#FF000000</item>
+        <item name="spacebarTextShadowColor">#D0FFFFFF</item>
+    </style>
     <!-- Theme "Gingerbread" -->
     <style
         name="Keyboard.Gingerbread"
         parent="Keyboard"
     >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">8</item>
         <item name="touchPositionCorrectionData">@array/touch_position_correction_data_gingerbread</item>
         <item name="horizontalGap">@fraction/key_horizontal_gap_gb</item>
@@ -213,16 +261,26 @@
         <item name="keyTextStyle">bold</item>
     </style>
     <style
-        name="MiniKeyboard.Gingerbread"
-        parent="Keyboard.Gingerbread"
+        name="LatinKeyboardView.Gingerbread"
+        parent="KeyboardView.Gingerbread"
     >
-        <item name="keyboardTopPadding">0dip</item>
-        <item name="keyboardBottomPadding">0dip</item>
-        <item name="horizontalGap">0dip</item>
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="spacebarTextColor">#FFC0C0C0</item>
+        <item name="spacebarTextShadowColor">#80000000</item>
     </style>
     <style
-        name="MiniKeyboardView.Gingerbread"
-        parent="MiniKeyboardView"
+        name="MoreKeysKeyboard.Gingerbread"
+        parent="Keyboard.Gingerbread"
+    >
+        <item name="keyboardTopPadding">0dp</item>
+        <item name="keyboardBottomPadding">0dp</item>
+        <item name="horizontalGap">0dp</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardView.Gingerbread"
+        parent="MoreKeysKeyboardView"
     >
         <item name="android:background">@null</item>
     </style>
@@ -231,6 +289,7 @@
         name="Keyboard.IceCreamSandwich"
         parent="Keyboard"
     >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">5</item>
         <item name="keyboardTopPadding">@fraction/keyboard_top_padding_ics</item>
         <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_ics</item>
@@ -239,12 +298,6 @@
         <item name="touchPositionCorrectionData">@array/touch_position_correction_data_ice_cream_sandwich</item>
     </style>
     <style
-        name="LatinKeyboard.IceCreamSandwich"
-        parent="LatinKeyboard"
-    >
-        <item name="disabledShortcutIcon">@drawable/sym_keyboard_voice_off_holo</item>
-    </style>
-    <style
         name="KeyboardView.IceCreamSandwich"
         parent="KeyboardView"
     >
@@ -254,38 +307,48 @@
         <item name="keyTextInactivatedColor">#66E0E4E5</item>
         <item name="keyHintLetterColor">#80000000</item>
         <item name="keyHintLabelColor">#A0FFFFFF</item>
-        <item name="keyUppercaseLetterInactivatedColor">#66E0E4E5</item>
-        <item name="keyUppercaseLetterActivatedColor">#FFFFFFFF</item>
+        <item name="keyShiftedLetterHintInactivatedColor">#66E0E4E5</item>
+        <item name="keyShiftedLetterHintActivatedColor">#FFFFFFFF</item>
         <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_ics</item>
         <item name="keyPreviewLeftBackground">@drawable/keyboard_key_feedback_left_ics</item>
         <item name="keyPreviewRightBackground">@drawable/keyboard_key_feedback_right_ics</item>
         <item name="keyPreviewBackgroundWidth">@dimen/keyboard_key_feedback_background_holo_width</item>
         <item name="keyPreviewBackgroundHeight">@dimen/keyboard_key_feedback_background_holo_height</item>
         <item name="keyPreviewTextColor">#FFFFFFFF</item>
-        <item name="keyPreviewHeight">@dimen/key_preview_height_ics</item>
         <item name="keyPreviewOffset">@dimen/key_preview_offset_ics</item>
         <item name="shadowColor">#00000000</item>
         <item name="shadowRadius">0.0</item>
     </style>
     <style
-        name="MiniKeyboard.IceCreamSandwich"
-        parent="Keyboard.IceCreamSandwich"
+        name="LatinKeyboardView.IceCreamSandwich"
+        parent="KeyboardView.IceCreamSandwich"
     >
-        <item name="keyboardTopPadding">0dip</item>
-        <item name="keyboardBottomPadding">0dip</item>
-        <item name="horizontalGap">0dip</item>
+        <item name="autoCorrectionSpacebarLedEnabled">false</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="spacebarTextColor">#FFC0C0C0</item>
+        <item name="spacebarTextShadowColor">#80000000</item>
     </style>
     <style
-        name="MiniKeyboardView.IceCreamSandwich"
-        parent="MiniKeyboardView"
+        name="MoreKeysKeyboard.IceCreamSandwich"
+        parent="Keyboard.IceCreamSandwich"
+    >
+        <item name="keyboardTopPadding">0dp</item>
+        <item name="keyboardBottomPadding">0dp</item>
+        <item name="horizontalGap">0dp</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardView.IceCreamSandwich"
+        parent="MoreKeysKeyboardView"
     >
         <item name="android:background">@null</item>
         <item name="keyBackground">@drawable/btn_keyboard_key_popup_ics</item>
+        <item name="verticalCorrection">@dimen/more_keys_keyboard_vertical_correction_ics</item>
     </style>
-    <style name="MiniKeyboardPanelStyle.IceCreamSandwich">
+    <style name="MoreKeysKeyboardPanelStyle.IceCreamSandwich">
         <item name="android:background">@drawable/keyboard_popup_panel_background_holo</item>
-        <item name="android:paddingLeft">@dimen/mini_keyboard_horizontal_edges_padding_ics</item>
-        <item name="android:paddingRight">@dimen/mini_keyboard_horizontal_edges_padding_ics</item>
+        <item name="android:paddingLeft">@dimen/more_keys_keyboard_horizontal_edges_padding_ics</item>
+        <item name="android:paddingRight">@dimen/more_keys_keyboard_horizontal_edges_padding_ics</item>
     </style>
     <style name="SuggestionsStripBackgroundStyle.IceCreamSandwich">
         <item name="android:background">@drawable/keyboard_suggest_strip_holo</item>
@@ -296,11 +359,12 @@
     >
         <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
         <!-- android:color/holo_blue_light=#FF33B5E5 -->
+        <item name="colorValidTypedWord">@android:color/holo_blue_light</item>
         <item name="colorTypedWord">@android:color/holo_blue_light</item>
         <item name="colorAutoCorrect">@android:color/holo_blue_light</item>
         <item name="colorSuggested">@android:color/holo_blue_light</item>
+        <item name="alphaValidTypedWord">85</item>
         <item name="alphaTypedWord">85</item>
-        <item name="alphaAutoCorrect">100</item>
         <item name="alphaSuggested">70</item>
         <item name="alphaObsoleted">70</item>
         <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
@@ -310,7 +374,7 @@
     </style>
     <style
         name="MoreSuggestionsViewStyle.IceCreamSandwich"
-        parent="MiniKeyboardView.IceCreamSandwich"
+        parent="MoreKeysKeyboardView.IceCreamSandwich"
     >
     </style>
     <style name="SuggestionBackgroundStyle.IceCreamSandwich">
@@ -318,11 +382,11 @@
     </style>
     <style
         name="SuggestionPreviewBackgroundStyle.IceCreamSandwich"
-        parent="MiniKeyboardPanelStyle.IceCreamSandwich"
+        parent="MoreKeysKeyboardPanelStyle.IceCreamSandwich"
     >
     </style>
-    <style name="MiniKeyboardAnimation">
-        <item name="android:windowEnterAnimation">@anim/mini_keyboard_fadein</item>
-        <item name="android:windowExitAnimation">@anim/mini_keyboard_fadeout</item>
+    <style name="MoreKeysKeyboardAnimation">
+        <item name="android:windowEnterAnimation">@anim/more_keys_keyboard_fadein</item>
+        <item name="android:windowExitAnimation">@anim/more_keys_keyboard_fadeout</item>
     </style>
 </resources>
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
index ba828a7..543992a 100644
--- a/java/res/values/sudden-jumping-touch-event-device-list.xml
+++ b/java/res/values/sudden-jumping-touch-event-device-list.xml
@@ -19,9 +19,9 @@
 -->
 <resources>
     <string-array name="sudden_jumping_touch_event_device_list" translatable="false">
-        <!-- Nexus One -->
-        <item>mahimahi</item>
-        <!-- Droid -->
-        <item>sholes</item>
+        <!-- "Build.HARDWARE,true" that needs "sudden jump touch event" hack.
+             See {@link com.android.inputmethod.keyboard.SuddenJumpingTouchEventHandler}. -->
+        <item>mahimahi,true</item> <!-- Nexus One -->
+        <item>sholes,true</item> <!-- Droid -->
     </string-array>
 </resources>
diff --git a/java/res/values/themes-basic-highcontrast.xml b/java/res/values/themes-basic-highcontrast.xml
index abb7c80..19df42c 100644
--- a/java/res/values/themes-basic-highcontrast.xml
+++ b/java/res/values/themes-basic-highcontrast.xml
@@ -17,11 +17,11 @@
 <resources>
     <style name="KeyboardTheme.HighContrast" parent="KeyboardIcons">
         <item name="keyboardStyle">@style/Keyboard.HighContrast</item>
-        <item name="latinKeyboardStyle">@style/LatinKeyboard</item>
         <item name="keyboardViewStyle">@style/KeyboardView.HighContrast</item>
-        <item name="miniKeyboardStyle">@style/MiniKeyboard</item>
-        <item name="miniKeyboardViewStyle">@style/MiniKeyboardView</item>
-        <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
+        <item name="latinKeyboardViewStyle">@style/LatinKeyboardView.HighContrast</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView</item>
+        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
         <item name="suggestionsStripBackgroundStyle">@style/SuggestionsStripBackgroundStyle</item>
         <item name="suggestionsViewStyle">@style/SuggestionsViewStyle</item>
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
diff --git a/java/res/values/themes-basic.xml b/java/res/values/themes-basic.xml
index ff9fed5..5d47720 100644
--- a/java/res/values/themes-basic.xml
+++ b/java/res/values/themes-basic.xml
@@ -17,11 +17,11 @@
 <resources>
     <style name="KeyboardTheme" parent="KeyboardIcons">
         <item name="keyboardStyle">@style/Keyboard</item>
-        <item name="latinKeyboardStyle">@style/LatinKeyboard</item>
         <item name="keyboardViewStyle">@style/KeyboardView</item>
-        <item name="miniKeyboardStyle">@style/MiniKeyboard</item>
-        <item name="miniKeyboardViewStyle">@style/MiniKeyboardView</item>
-        <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
+        <item name="latinKeyboardViewStyle">@style/LatinKeyboardView</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView</item>
+        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
         <item name="suggestionsStripBackgroundStyle">@style/SuggestionsStripBackgroundStyle</item>
         <item name="suggestionsViewStyle">@style/SuggestionsViewStyle</item>
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
diff --git a/java/res/values/themes-gingerbread.xml b/java/res/values/themes-gingerbread.xml
index be853eb..a139798 100644
--- a/java/res/values/themes-gingerbread.xml
+++ b/java/res/values/themes-gingerbread.xml
@@ -17,11 +17,11 @@
 <resources>
     <style name="KeyboardTheme.Gingerbread" parent="KeyboardIcons">
         <item name="keyboardStyle">@style/Keyboard.Gingerbread</item>
-        <item name="latinKeyboardStyle">@style/LatinKeyboard</item>
         <item name="keyboardViewStyle">@style/KeyboardView.Gingerbread</item>
-        <item name="miniKeyboardStyle">@style/MiniKeyboard.Gingerbread</item>
-        <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.Gingerbread</item>
-        <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
+        <item name="latinKeyboardViewStyle">@style/LatinKeyboardView.Gingerbread</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.Gingerbread</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.Gingerbread</item>
+        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
         <item name="suggestionsStripBackgroundStyle">@style/SuggestionsStripBackgroundStyle</item>
         <item name="suggestionsViewStyle">@style/SuggestionsViewStyle</item>
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 618aaed..e6fd4f4 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -17,11 +17,11 @@
 <resources>
     <style name="KeyboardTheme.IceCreamSandwich" parent="KeyboardIcons.IceCreamSandwich">
         <item name="keyboardStyle">@style/Keyboard.IceCreamSandwich</item>
-        <item name="latinKeyboardStyle">@style/LatinKeyboard.IceCreamSandwich</item>
         <item name="keyboardViewStyle">@style/KeyboardView.IceCreamSandwich</item>
-        <item name="miniKeyboardStyle">@style/MiniKeyboard.IceCreamSandwich</item>
-        <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.IceCreamSandwich</item>
-        <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle.IceCreamSandwich</item>
+        <item name="latinKeyboardViewStyle">@style/LatinKeyboardView.IceCreamSandwich</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.IceCreamSandwich</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.IceCreamSandwich</item>
+        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle.IceCreamSandwich</item>
         <item name="suggestionsStripBackgroundStyle">@style/SuggestionsStripBackgroundStyle.IceCreamSandwich</item>
         <item name="suggestionsViewStyle">@style/SuggestionsViewStyle.IceCreamSandwich</item>
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle.IceCreamSandwich</item>
diff --git a/java/res/values/themes-stone-bold.xml b/java/res/values/themes-stone-bold.xml
index 532a298..47de99e 100644
--- a/java/res/values/themes-stone-bold.xml
+++ b/java/res/values/themes-stone-bold.xml
@@ -17,11 +17,11 @@
 <resources>
     <style name="KeyboardTheme.Stone.Bold" parent="KeyboardIcons.Black">
         <item name="keyboardStyle">@style/Keyboard.Stone.Bold</item>
-        <item name="latinKeyboardStyle">@style/LatinKeyboard.Stone</item>
         <item name="keyboardViewStyle">@style/KeyboardView.Stone.Bold</item>
-        <item name="miniKeyboardStyle">@style/MiniKeyboard.Stone</item>
-        <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.Stone</item>
-        <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
+        <item name="latinKeyboardViewStyle">@style/LatinKeyboardView.Stone.Bold</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.Stone</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.Stone</item>
+        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
         <item name="suggestionsStripBackgroundStyle">@style/SuggestionsStripBackgroundStyle</item>
         <item name="suggestionsViewStyle">@style/SuggestionsViewStyle</item>
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
diff --git a/java/res/values/themes-stone.xml b/java/res/values/themes-stone.xml
index cb3edc5..a0b39e3 100644
--- a/java/res/values/themes-stone.xml
+++ b/java/res/values/themes-stone.xml
@@ -17,11 +17,11 @@
 <resources>
     <style name="KeyboardTheme.Stone" parent="KeyboardIcons.Black">
         <item name="keyboardStyle">@style/Keyboard.Stone</item>
-        <item name="latinKeyboardStyle">@style/LatinKeyboard.Stone</item>
         <item name="keyboardViewStyle">@style/KeyboardView.Stone</item>
-        <item name="miniKeyboardStyle">@style/MiniKeyboard.Stone</item>
-        <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.Stone</item>
-        <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
+        <item name="latinKeyboardViewStyle">@style/LatinKeyboardView.Stone</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.Stone</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.Stone</item>
+        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
         <item name="suggestionsStripBackgroundStyle">@style/SuggestionsStripBackgroundStyle</item>
         <item name="suggestionsViewStyle">@style/SuggestionsViewStyle</item>
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
diff --git a/java/res/values/touch-position-correction.xml b/java/res/values/touch-position-correction.xml
index 0a0e4e5..41b435a 100644
--- a/java/res/values/touch-position-correction.xml
+++ b/java/res/values/touch-position-correction.xml
@@ -71,4 +71,4 @@
         <item>0.0880847</item>
         <item>0.1522819</item>
     </string-array>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/java/res/xml-ar/kbd_qwerty.xml b/java/res/xml-ar/kbd_qwerty.xml
deleted file mode 100644
index b26a938..0000000
--- a/java/res/xml-ar/kbd_qwerty.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="ar"
-    latin:isRtlKeyboard="true"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_arabic" />
-</Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml-ar/kbd_symbols.xml
deleted file mode 100644
index 9e5c255..0000000
--- a/java/res/xml-ar/kbd_symbols.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:isRtlKeyboard="true"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
-</Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols_shift.xml b/java/res/xml-ar/kbd_symbols_shift.xml
deleted file mode 100644
index 934e6f8..0000000
--- a/java/res/xml-ar/kbd_symbols_shift.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:isRtlKeyboard="true"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml-ar/keyboard_set.xml b/java/res/xml-ar/keyboard_set.xml
new file mode 100644
index 0000000..88d3201
--- /dev/null
+++ b/java/res/xml-ar/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_arabic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-be/keyboard_set.xml b/java/res/xml-be/keyboard_set.xml
new file mode 100644
index 0000000..959f644
--- /dev/null
+++ b/java/res/xml-be/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-bg/keyboard_set.xml b/java/res/xml-bg/keyboard_set.xml
new file mode 100644
index 0000000..593ad97
--- /dev/null
+++ b/java/res/xml-bg/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_bulgarian"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-cs/kbd_qwerty.xml b/java/res/xml-cs/kbd_qwerty.xml
deleted file mode 100644
index 9991ea2..0000000
--- a/java/res/xml-cs/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="cs"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
-</Keyboard>
diff --git a/java/res/xml-cs/keyboard_set.xml b/java/res/xml-cs/keyboard_set.xml
new file mode 100644
index 0000000..f9f7451
--- /dev/null
+++ b/java/res/xml-cs/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-da/keyboard_set.xml b/java/res/xml-da/keyboard_set.xml
new file mode 100644
index 0000000..0db9b1f
--- /dev/null
+++ b/java/res/xml-da/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-de-rQY/keyboard_set.xml b/java/res/xml-de-rQY/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-de-rQY/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml-de-rZZ/kbd_qwerty.xml
deleted file mode 100644
index d5fd8ef..0000000
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</Keyboard>
diff --git a/java/res/xml-de/kbd_qwerty.xml b/java/res/xml-de/kbd_qwerty.xml
deleted file mode 100644
index 89e10b2..0000000
--- a/java/res/xml-de/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
-</Keyboard>
diff --git a/java/res/xml-de/keyboard_set.xml b/java/res/xml-de/keyboard_set.xml
new file mode 100644
index 0000000..f9f7451
--- /dev/null
+++ b/java/res/xml-de/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-el/keyboard_set.xml b/java/res/xml-el/keyboard_set.xml
new file mode 100644
index 0000000..af74e12
--- /dev/null
+++ b/java/res/xml-el/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_greek"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-es/kbd_qwerty.xml b/java/res/xml-es/kbd_qwerty.xml
deleted file mode 100644
index 568f4d6..0000000
--- a/java/res/xml-es/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="es,es_US"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_spanish" />
-</Keyboard>
diff --git a/java/res/xml-es/keyboard_set.xml b/java/res/xml-es/keyboard_set.xml
new file mode 100644
index 0000000..4ff5b54
--- /dev/null
+++ b/java/res/xml-es/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_spanish"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_spanish_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_spanish_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-et/keyboard_set.xml b/java/res/xml-et/keyboard_set.xml
new file mode 100644
index 0000000..0db9b1f
--- /dev/null
+++ b/java/res/xml-et/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fa/keyboard_set.xml b/java/res/xml-fa/keyboard_set.xml
new file mode 100644
index 0000000..f508f8f
--- /dev/null
+++ b/java/res/xml-fa/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_farsi"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fi/kbd_qwerty.xml b/java/res/xml-fi/kbd_qwerty.xml
deleted file mode 100644
index 75721e0..0000000
--- a/java/res/xml-fi/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="fi"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
diff --git a/java/res/xml-fi/keyboard_set.xml b/java/res/xml-fi/keyboard_set.xml
new file mode 100644
index 0000000..0db9b1f
--- /dev/null
+++ b/java/res/xml-fi/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml-fr-rCA/kbd_qwerty.xml
deleted file mode 100644
index 7bdfbad..0000000
--- a/java/res/xml-fr-rCA/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="fr_CA"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</Keyboard>
diff --git a/java/res/xml-fr-rCA/keyboard_set.xml b/java/res/xml-fr-rCA/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-fr-rCA/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fr-rCH/kbd_qwerty.xml b/java/res/xml-fr-rCH/kbd_qwerty.xml
deleted file mode 100644
index 41b701d..0000000
--- a/java/res/xml-fr-rCH/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="fr_CH"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
-</Keyboard>
diff --git a/java/res/xml-fr-rCH/keyboard_set.xml b/java/res/xml-fr-rCH/keyboard_set.xml
new file mode 100644
index 0000000..f9f7451
--- /dev/null
+++ b/java/res/xml-fr-rCH/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fr/kbd_qwerty.xml b/java/res/xml-fr/kbd_qwerty.xml
deleted file mode 100644
index 8c730a2..0000000
--- a/java/res/xml-fr/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="fr"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_azerty" />
-</Keyboard>
diff --git a/java/res/xml-fr/keyboard_set.xml b/java/res/xml-fr/keyboard_set.xml
new file mode 100644
index 0000000..2ac25c9
--- /dev/null
+++ b/java/res/xml-fr/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_azerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_azerty_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_azerty_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-hi/keyboard_set.xml b/java/res/xml-hi/keyboard_set.xml
new file mode 100644
index 0000000..c1fd071
--- /dev/null
+++ b/java/res/xml-hi/keyboard_set.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_hindi"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_hindi"
+        latin:enableProximityCharsCorrection="true" />
+    <!-- On these shifted alphabet layouts the proximity characters correction should be disabled
+         because the letters on these layouts aren't the ones in different case of the above
+         unshifted layouts. -->
+    <Element
+        latin:elementName="alphabetManualShifted"
+        latin:elementKeyboard="@xml/kbd_hindi" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_hindi" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_hindi" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-hr/kbd_qwerty.xml b/java/res/xml-hr/kbd_qwerty.xml
deleted file mode 100644
index ca92e86..0000000
--- a/java/res/xml-hr/kbd_qwerty.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hr"
->
-    <!-- TODO: Dedicated Croatian layout especially for tablet. -->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
-</Keyboard>
diff --git a/java/res/xml-hr/keyboard_set.xml b/java/res/xml-hr/keyboard_set.xml
new file mode 100644
index 0000000..f9f7451
--- /dev/null
+++ b/java/res/xml-hr/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-hu/keyboard_set.xml b/java/res/xml-hu/keyboard_set.xml
new file mode 100644
index 0000000..f9f7451
--- /dev/null
+++ b/java/res/xml-hu/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-is/keyboard_set.xml b/java/res/xml-is/keyboard_set.xml
new file mode 100644
index 0000000..44edbba
--- /dev/null
+++ b/java/res/xml-is/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-iw/kbd_qwerty.xml b/java/res/xml-iw/kbd_qwerty.xml
deleted file mode 100644
index 54cd4b5..0000000
--- a/java/res/xml-iw/kbd_qwerty.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="iw"
-    latin:isRtlKeyboard="true"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_hebrew" />
-</Keyboard>
diff --git a/java/res/xml-iw/kbd_symbols.xml b/java/res/xml-iw/kbd_symbols.xml
deleted file mode 100644
index 9e5c255..0000000
--- a/java/res/xml-iw/kbd_symbols.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:isRtlKeyboard="true"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
-</Keyboard>
diff --git a/java/res/xml-iw/kbd_symbols_shift.xml b/java/res/xml-iw/kbd_symbols_shift.xml
deleted file mode 100644
index 934e6f8..0000000
--- a/java/res/xml-iw/kbd_symbols_shift.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:isRtlKeyboard="true"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml-iw/keyboard_set.xml b/java/res/xml-iw/keyboard_set.xml
new file mode 100644
index 0000000..6c51fb0
--- /dev/null
+++ b/java/res/xml-iw/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_hebrew"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_hebrew_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_hebrew_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-ka/keyboard_set.xml b/java/res/xml-ka/keyboard_set.xml
new file mode 100644
index 0000000..bc3df1e
--- /dev/null
+++ b/java/res/xml-ka/keyboard_set.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_georgian"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_georgian"
+        latin:enableProximityCharsCorrection="true" />
+    <!-- On these shifted alphabet layouts the proximity characters correction should be disabled
+         because the letters on these layouts aren't the ones in different case of the above
+         unshifted layouts. -->
+    <Element
+        latin:elementName="alphabetManualShifted"
+        latin:elementKeyboard="@xml/kbd_georgian" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_georgian" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_georgian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-ky/keyboard_set.xml b/java/res/xml-ky/keyboard_set.xml
new file mode 100644
index 0000000..959f644
--- /dev/null
+++ b/java/res/xml-ky/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-land/kbd_number.xml b/java/res/xml-land/kbd_number.xml
index f5930ef..7cc0fb2 100644
--- a/java/res/xml-land/kbd_number.xml
+++ b/java/res/xml-land/kbd_number.xml
@@ -24,5 +24,5 @@
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_number" />
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-land/kbd_phone.xml b/java/res/xml-land/kbd_phone.xml
index 3b1fb36..aa54b83 100644
--- a/java/res/xml-land/kbd_phone.xml
+++ b/java/res/xml-land/kbd_phone.xml
@@ -24,5 +24,5 @@
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_phone" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-land/kbd_phone_shift.xml b/java/res/xml-land/kbd_phone_symbols.xml
similarity index 93%
rename from java/res/xml-land/kbd_phone_shift.xml
rename to java/res/xml-land/kbd_phone_symbols.xml
index e596647..41ba6cf 100644
--- a/java/res/xml-land/kbd_phone_shift.xml
+++ b/java/res/xml-land/kbd_phone_symbols.xml
@@ -24,5 +24,5 @@
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
+        latin:keyboardLayout="@xml/rows_phone_symbols" />
 </Keyboard>
diff --git a/java/res/xml-mk/keyboard_set.xml b/java/res/xml-mk/keyboard_set.xml
new file mode 100644
index 0000000..6b8b844
--- /dev/null
+++ b/java/res/xml-mk/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_south_slavic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-nb/kbd_qwerty.xml b/java/res/xml-nb/kbd_qwerty.xml
deleted file mode 100644
index 1f4e86e..0000000
--- a/java/res/xml-nb/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="nb"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
diff --git a/java/res/xml-nb/keyboard_set.xml b/java/res/xml-nb/keyboard_set.xml
new file mode 100644
index 0000000..0db9b1f
--- /dev/null
+++ b/java/res/xml-nb/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-pl/kbd_qwerty.xml b/java/res/xml-pl/kbd_qwerty.xml
deleted file mode 100644
index 44312c5..0000000
--- a/java/res/xml-pl/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="pl"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</Keyboard>
diff --git a/java/res/xml-pl/keyboard_set.xml b/java/res/xml-pl/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-pl/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-pt/kbd_qwerty.xml b/java/res/xml-pt/kbd_qwerty.xml
deleted file mode 100644
index f5dcbc6..0000000
--- a/java/res/xml-pt/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="pt"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</Keyboard>
diff --git a/java/res/xml-pt/keyboard_set.xml b/java/res/xml-pt/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-pt/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-ro/keyboard_set.xml b/java/res/xml-ro/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-ro/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-ru/kbd_qwerty.xml b/java/res/xml-ru/kbd_qwerty.xml
deleted file mode 100644
index aee1b1b..0000000
--- a/java/res/xml-ru/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="ru"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_russian" />
-</Keyboard>
diff --git a/java/res/xml-ru/keyboard_set.xml b/java/res/xml-ru/keyboard_set.xml
new file mode 100644
index 0000000..959f644
--- /dev/null
+++ b/java/res/xml-ru/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sk/keyboard_set.xml b/java/res/xml-sk/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-sk/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sl/keyboard_set.xml b/java/res/xml-sl/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-sl/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sr/kbd_qwerty.xml b/java/res/xml-sr/kbd_qwerty.xml
deleted file mode 100644
index 58fc187..0000000
--- a/java/res/xml-sr/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sr"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_serbian" />
-</Keyboard>
diff --git a/java/res/xml-sr/keyboard_set.xml b/java/res/xml-sr/keyboard_set.xml
new file mode 100644
index 0000000..5098134
--- /dev/null
+++ b/java/res/xml-sr/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_south_slavic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sv/keyboard_set.xml b/java/res/xml-sv/keyboard_set.xml
new file mode 100644
index 0000000..0db9b1f
--- /dev/null
+++ b/java/res/xml-sv/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sw600dp-land/kbd_mini_keyboard_template.xml b/java/res/xml-sw600dp-land/kbd_more_keys_keyboard_template.xml
similarity index 95%
rename from java/res/xml-sw600dp-land/kbd_mini_keyboard_template.xml
rename to java/res/xml-sw600dp-land/kbd_more_keys_keyboard_template.xml
index 8272e02..4d8b446 100644
--- a/java/res/xml-sw600dp-land/kbd_mini_keyboard_template.xml
+++ b/java/res/xml-sw600dp-land/kbd_more_keys_keyboard_template.xml
@@ -21,6 +21,6 @@
 <Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="5%p"
     latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/miniKeyboardStyle"
+    style="?attr/moreKeysKeyboardStyle"
     >
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw600dp-land/kbd_number.xml
similarity index 85%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_number.xml
index 3195d5b..9d358b6 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_number.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw600dp-land/kbd_phone.xml
similarity index 85%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_phone.xml
index 3195d5b..abac6bd 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_phone.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw600dp-land/kbd_phone_symbols.xml
similarity index 80%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_phone_symbols.xml
index 3195d5b..e3f56bc 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_phone_symbols.xml
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp-land/kbd_thai.xml
similarity index 77%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_thai.xml
index e29d9ab..b75980f 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_thai.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="3.20%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_azerty_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_azerty_symbols.xml
index e29d9ab..66254de 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_azerty_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_azerty_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_azerty_symbols_shift.xml
index e29d9ab..3c5ed5e 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_azerty_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_hebrew_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_hebrew_symbols.xml
index e29d9ab..66254de 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_hebrew_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_hebrew_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_hebrew_symbols_shift.xml
index e29d9ab..3c5ed5e 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_hebrew_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_key_styles.xml b/java/res/xml-sw600dp/kbd_key_styles.xml
deleted file mode 100644
index 25fa8b2..0000000
--- a/java/res/xml-sw600dp/kbd_key_styles.xml
+++ /dev/null
@@ -1,114 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- Base key style for the key which may have settings key as popup key -->
-    <switch>
-        <case
-            latin:clobberSettingsKey="true"
-        >
-            <key-style
-                latin:styleName="f2PopupStyle"
-                latin:backgroundType="functional" />
-        </case>
-        <default>
-            <key-style
-                latin:styleName="f2PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="\@icon/3|\@integer/key_settings"
-                latin:backgroundType="functional" />
-        </default>
-    </switch>
-    <!-- Functional key styles -->
-    <key-style
-        latin:styleName="shiftKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyIcon="iconShiftKey"
-        latin:keyIconShifted="iconShiftedShiftKey"
-        latin:backgroundType="sticky" />
-    <key-style
-        latin:styleName="deleteKeyStyle"
-        latin:code="@integer/key_delete"
-        latin:keyIcon="iconDeleteKey"
-        latin:backgroundType="functional"
-        latin:isRepeatable="true" />
-    <key-style
-        latin:styleName="returnKeyStyle"
-        latin:code="@integer/key_return"
-        latin:keyIcon="iconReturnKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="spaceKeyStyle"
-        latin:code="@integer/key_space" />
-    <key-style
-        latin:styleName="nonSpecialBackgroundSpaceKeyStyle"
-        latin:code="@integer/key_space" />
-    <key-style
-        latin:styleName="smileyKeyStyle"
-        latin:keyLabel=":-)"
-        latin:keyOutputText=":-) "
-        latin:keyLabelOption="hasPopupHint"
-        latin:moreKeys="@string/more_keys_for_smiley"
-        latin:maxMoreKeysColumn="5" />
-    <key-style
-        latin:styleName="shortcutKeyStyle"
-        latin:code="@integer/key_shortcut"
-        latin:keyIcon="iconShortcutKey"
-        latin:parentStyle="f2PopupStyle" />
-    <key-style
-        latin:styleName="settingsKeyStyle"
-        latin:code="@integer/key_settings"
-        latin:keyIcon="iconSettingsKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="tabKeyStyle"
-        latin:code="@integer/key_tab"
-        latin:keyIcon="iconTabKey"
-        latin:keyIconPreview="iconPreviewTabKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toSymbolKeyStyle"
-        latin:code="@integer/key_switch_alpha_symbol"
-        latin:keyLabel="@string/label_to_symbol_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toAlphaKeyStyle"
-        latin:code="@integer/key_switch_alpha_symbol"
-        latin:keyLabel="@string/label_to_alpha_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toMoreSymbolKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyLabel="@string/label_to_more_symbol_for_tablet_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="backFromMoreSymbolKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyLabel="@string/label_to_symbol_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="comKeyStyle"
-        latin:keyLabel="@string/keylabel_for_popular_domain"
-        latin:keyLabelOption="fontNormal|hasPopupHint"
-        latin:keyOutputText="@string/keylabel_for_popular_domain"
-        latin:moreKeys="@string/more_keys_for_popular_domain" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_mini_keyboard_template.xml b/java/res/xml-sw600dp/kbd_more_keys_keyboard_template.xml
similarity index 95%
rename from java/res/xml-sw600dp/kbd_mini_keyboard_template.xml
rename to java/res/xml-sw600dp/kbd_more_keys_keyboard_template.xml
index 0d5795f..d90a588 100644
--- a/java/res/xml-sw600dp/kbd_mini_keyboard_template.xml
+++ b/java/res/xml-sw600dp/kbd_more_keys_keyboard_template.xml
@@ -21,6 +21,6 @@
 <Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="8%p"
     latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/miniKeyboardStyle"
+    style="?attr/moreKeysKeyboardStyle"
     >
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_number.xml b/java/res/xml-sw600dp/kbd_number.xml
index 46114de..70cf6a2 100644
--- a/java/res/xml-sw600dp/kbd_number.xml
+++ b/java/res/xml-sw600dp/kbd_number.xml
@@ -20,190 +20,8 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="16.75%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
-    <switch>
-        <case
-            latin:passwordInput="true"
-        >
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="24.875%p" />
-                <Key
-                    latin:keyStyle="num1KeyStyle" />
-                <Key
-                    latin:keyStyle="num2KeyStyle" />
-                <Key
-                    latin:keyStyle="num3KeyStyle" />
-                <Key
-                    latin:keyStyle="deleteKeyStyle"
-                    latin:keyXPos="-11.00%p"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="24.875%p" />
-                <Key
-                    latin:keyStyle="num4KeyStyle" />
-                <Key
-                    latin:keyStyle="num5KeyStyle" />
-                <Key
-                    latin:keyStyle="num6KeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyXPos="-11.00%p"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="24.875%p" />
-                <Key
-                    latin:keyStyle="num7KeyStyle" />
-                <Key
-                    latin:keyStyle="num8KeyStyle" />
-                <Key
-                    latin:keyStyle="num9KeyStyle" />
-                <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                <Spacer
-                    latin:keyWidth="0%p" />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyStyle="tabKeyStyle"
-                    latin:keyWidth="11.00%p" />
-                <Spacer
-                    latin:keyXPos="24.875%p" />
-                <Key
-                    latin:keyStyle="num0KeyStyle" />
-                <Spacer
-                    latin:keyXPos="-11.00%p"
-                    latin:keyWidth="0%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_f2" />
-            </Row>
-        </case>
-        <!-- latin:passwordInput="false" -->
-        <default>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="11.00%p" />
-                <Key
-                    latin:keyLabel="-"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="+"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="1"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="38.75%p" />
-                <Key
-                    latin:keyLabel="2"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="3"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyStyle="deleteKeyStyle"
-                    latin:keyXPos="-11.00%p"
-                    latin:keyWidth="fillBoth" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="11.00%p" />
-                <Key
-                    latin:keyStyle="numStarKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="/"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel=","
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="4"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="38.75%p" />
-                <Key
-                    latin:keyLabel="5"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="6"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyXPos="-11.00%p"
-                    latin:keyWidth="fillBoth" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="11.00%p" />
-                <Key
-                    latin:keyLabel="("
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel=")"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="="
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="9.25%p" />
-                <Key
-                    latin:keyLabel="7"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="38.75%p" />
-                <Key
-                    latin:keyLabel="8"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="9"
-                    latin:keyStyle="numKeyStyle" />
-                <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                <Spacer
-                    latin:keyWidth="0%p" />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyStyle="tabKeyStyle"
-                    latin:keyWidth="11.00%p" />
-                <Key
-                    latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
-                    latin:keyWidth="27.75%p" />
-                <Key
-                    latin:keyStyle="numStarKeyStyle"
-                    latin:keyXPos="38.75%p" />
-                <Key
-                    latin:keyLabel="0"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="#"
-                    latin:keyStyle="numKeyStyle" />
-                <Spacer
-                    latin:keyXPos="-11.00%p"
-                    latin:keyWidth="0%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_f2" />
-            </Row>
-        </default>
-    </switch>
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_phone.xml b/java/res/xml-sw600dp/kbd_phone.xml
index 303f814..72acef2 100644
--- a/java/res/xml-sw600dp/kbd_phone.xml
+++ b/java/res/xml-sw600dp/kbd_phone.xml
@@ -20,104 +20,8 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="16.75%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="15.625%p" />
-        <Key
-            latin:keyLabel="-"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="9.25%p" />
-        <Key
-            latin:keyLabel="+"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="9.25%p" />
-        <Key
-            latin:keyStyle="num1KeyStyle"
-            latin:keyXPos="38.867%p" />
-        <Key
-            latin:keyStyle="num2KeyStyle" />
-        <Key
-            latin:keyStyle="num3KeyStyle" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.00%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="15.625%p" />
-        <Key
-            latin:keyLabel=","
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="9.25%p" />
-        <Key
-            latin:keyLabel="."
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="9.25%p" />
-        <Key
-            latin:keyStyle="num4KeyStyle"
-            latin:keyXPos="38.867%p" />
-        <Key
-            latin:keyStyle="num5KeyStyle" />
-        <Key
-            latin:keyStyle="num6KeyStyle" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-11.00%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row>
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="11.0%p" />
-        <Key
-            latin:keyLabel="("
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="15.625%p"
-            latin:keyWidth="9.25%p" />
-        <Key
-            latin:keyLabel=")"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="9.25%p" />
-        <Key
-            latin:keyStyle="num7KeyStyle"
-            latin:keyXPos="38.867%p" />
-        <Key
-            latin:keyStyle="num8KeyStyle" />
-        <Key
-            latin:keyStyle="num9KeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="0%p" />
-        </Row>
-    <Row>
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyWidth="11.00%p" />
-        <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
-            latin:keyXPos="15.625%p"
-            latin:keyWidth="18.50%p" />
-        <Key
-            latin:keyStyle="numStarKeyStyle"
-            latin:keyXPos="38.867%p" />
-        <Key
-            latin:keyStyle="num0KeyStyle" />
-        <Key
-            latin:keyLabel="#"
-            latin:keyStyle="numKeyStyle" />
-        <Spacer
-            latin:keyXPos="-11.00%p"
-            latin:keyWidth="0%p" />
-        <include
-            latin:keyboardLayout="@xml/kbd_qwerty_f2" />
-    </Row>
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_phone_symbols.xml
similarity index 84%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_phone_symbols.xml
index 3195d5b..9faeaf4 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_phone_symbols.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyWidth="15.00%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_qwerty_f2.xml b/java/res/xml-sw600dp/kbd_qwerty_f2.xml
deleted file mode 100644
index b25afc1..0000000
--- a/java/res/xml-sw600dp/kbd_qwerty_f2.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:f2KeyMode="settings"
-        >
-            <Key
-                latin:keyStyle="settingsKeyStyle"
-                latin:keyWidth="fillBoth" />
-        </case>
-        <case
-            latin:f2KeyMode="shortcutIme"
-        >
-            <switch>
-                <case
-                    latin:shortcutKeyEnabled="true"
-                >
-                    <Key
-                        latin:keyStyle="shortcutKeyStyle"
-                        latin:keyWidth="fillBoth" />
-                </case>
-                <!-- shortcutKeyEnabled="false" -->
-                <default>
-                    <Spacer />
-                </default>
-            </switch>
-        </case>
-        <case
-            latin:f2KeyMode="shortcutImeOrSettings"
-        >
-            <switch>
-                <case
-                    latin:shortcutKeyEnabled="true"
-                >
-                    <Key
-                        latin:keyStyle="shortcutKeyStyle"
-                        latin:keyWidth="fillBoth" />
-                </case>
-                <!-- shortcutKeyEnabled="false" -->
-                <default>
-                    <Key
-                        latin:keyStyle="settingsKeyStyle"
-                        latin:keyWidth="fillBoth" />
-                </default>
-            </switch>
-        </case>
-        <!-- f2KeyMode="none" -->
-        <default>
-            <Spacer />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_qwerty_row1.xml b/java/res/xml-sw600dp/kbd_qwerty_row1.xml
deleted file mode 100644
index 07d8e22..0000000
--- a/java/res/xml-sw600dp/kbd_qwerty_row1.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_qwerty_row2.xml b/java/res/xml-sw600dp/kbd_qwerty_row2.xml
deleted file mode 100644
index 52a948f..0000000
--- a/java/res/xml-sw600dp/kbd_qwerty_row2.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a"
-            latin:keyXPos="4.5%p" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_qwerty_row3.xml b/java/res/xml-sw600dp/kbd_qwerty_row3.xml
deleted file mode 100644
index 4dabf63..0000000
--- a/java/res/xml-sw600dp/kbd_qwerty_row3.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_smiley" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_arabic.xml b/java/res/xml-sw600dp/kbd_rows_arabic.xml
deleted file mode 100644
index c2d3cd4..0000000
--- a/java/res/xml-sw600dp/kbd_rows_arabic.xml
+++ /dev/null
@@ -1,217 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="8.0%p"
-    >
-        <!-- \u0636: ARABIC LETTER DAD -->
-        <Key
-            latin:keyLabel="ض" />
-        <!-- \u0635: ARABIC LETTER SAD -->
-        <Key
-            latin:keyLabel="ص" />
-        <!-- \u062b: ARABIC LETTER THEH -->
-        <Key
-            latin:keyLabel="ث" />
-        <!-- \u0642: ARABIC LETTER QAF
-             \u06a8: ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
-        <Key
-            latin:keyLabel="ق"
-            latin:moreKeys="ڨ" />
-        <!-- \u0641: ARABIC LETTER FEH
-             \u06a4: ARABIC LETTER VEH
-             \u06a2: ARABIC LETTER FEH WITH DOT MOVED BELOW
-             \u06a5: ARABIC LETTER FEH WITH THREE DOTS BELOW -->
-        <Key
-            latin:keyLabel="ف"
-            latin:moreKeys="\u06a4,\u06a2,\u06a5" />
-        <!-- \u063a: ARABIC LETTER GHAIN -->
-        <Key
-            latin:keyLabel="غ" />
-        <!-- \u0639: ARABIC LETTER AIN -->
-        <Key
-            latin:keyLabel="ع" />
-        <!-- \u0647: ARABIC LETTER HEH
-             \ufeeb: ARABIC LETTER HEH INITIAL FORM
-             \u0647\u0640: ARABIC LETTER HEH + Zero width joiner -->
-        <Key
-            latin:keyLabel="ه"
-            latin:moreKeys="\ufeeb|\u0647\u200D" />
-        <!-- \u062e: ARABIC LETTER KHAH -->
-        <Key
-            latin:keyLabel="خ" />
-        <!-- \u062d: ARABIC LETTER HAH -->
-        <Key
-            latin:keyLabel="ح" />
-        <!-- \u062c: ARABIC LETTER JEEM
-             \u0686: ARABIC LETTER TCHEH -->
-        <Key
-            latin:keyLabel="ج"
-            latin:moreKeys="چ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.0%p"
-    >
-        <!-- \u0634: ARABIC LETTER SHEEN
-             \u069c: ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-        <Key
-            latin:keyLabel="ش"
-            latin:moreKeys="ڜ"
-            latin:keyXPos="3.0%p" />
-        <!-- \u0633: ARABIC LETTER SEEN -->
-        <Key
-            latin:keyLabel="س" />
-        <!-- \u064a: ARABIC LETTER YEH
-             \u0626: ARABIC LETTER YEH WITH HAMZA ABOVE
-             \u0649: ARABIC LETTER ALEF MAKSURA -->
-        <Key
-            latin:keyLabel="ي"
-            latin:moreKeys="\u0626,\u0649" />
-        <!-- \u0628: ARABIC LETTER BEH
-             \u067e: ARABIC LETTER PEH -->
-        <Key
-            latin:keyLabel="ب"
-            latin:moreKeys="پ" />
-        <!-- \u0644: ARABIC LETTER LAM
-             \ufefb: ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
-             \u0627: ARABIC LETTER ALEF
-             \ufef7: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
-             \u0623: ARABIC LETTER ALEF WITH HAMZA ABOVE
-             \ufef9: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
-             \u0625: ARABIC LETTER ALEF WITH HAMZA BELOW
-             \ufef5: ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
-             \u0622: ARABIC LETTER ALEF WITH MADDA ABOVE -->
-        <Key
-            latin:keyLabel="ل"
-            latin:moreKeys="\ufefb|\u0644\u0627,\ufef7|\u0644\u0623,\ufef9|\u0644\u0625,\ufef5|\u0644\u0622" />
-        <!-- \u0627: ARABIC LETTER ALEF
-             \u0621: ARABIC LETTER HAMZA
-             \u0671: ARABIC LETTER ALEF WASLA
-             \u0623: ARABIC LETTER ALEF WITH HAMZA ABOVE
-             \u0625: ARABIC LETTER ALEF WITH HAMZA BELOW
-             \u0622: ARABIC LETTER ALEF WITH MADDA ABOVE -->
-        <Key
-            latin:keyLabel="ا"
-            latin:moreKeys="\u0621,\u0671,\u0623,\u0625,\u0622" />
-        <!-- \u062a: ARABIC LETTER TEH -->
-        <Key
-            latin:keyLabel="ت" />
-        <!-- \u0646: ARABIC LETTER NOON -->
-        <Key
-            latin:keyLabel="ن" />
-        <!-- \u0645: ARABIC LETTER MEEM -->
-        <Key
-            latin:keyLabel="م" />
-        <!-- \u0643: ARABIC LETTER KAF
-             \u06af: ARABIC LETTER GAF
-             \u06a9: ARABIC LETTER KEHEH -->
-        <Key
-            latin:keyLabel="ك"
-            latin:moreKeys="\u06af,\u06a9" />
-        <!-- \u0637: ARABIC LETTER TAH -->
-        <Key
-            latin:keyLabel="ط" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.0%p"
-    >
-    <!-- kbd_row3_smiley -->
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="\@" />
-            </case>
-            <case
-                latin:mode="url"
-            >
-                <Key
-                    latin:keyLabel="-"
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="_"
-                    latin:moreKeys="_" />
-            </case>
-            <case
-                latin:imeAction="actionSearch"
-            >
-                <Key
-                    latin:keyLabel=":"
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="+"
-                    latin:moreKeys="+" />
-            </case>
-            <default>
-                <Key
-                    latin:keyStyle="smileyKeyStyle" />
-            </default>
-        </switch>
-        <!-- \u0626: ARABIC LETTER YEH WITH HAMZA ABOVE -->
-        <Key
-            latin:keyLabel="ئ" />
-        <!-- \u0621: ARABIC LETTER HAMZA -->
-        <Key
-            latin:keyLabel="ء" />
-        <!-- \u0624: ARABIC LETTER WAW WITH HAMZA ABOVE -->
-        <Key
-            latin:keyLabel="ؤ" />
-        <!-- \u0631: ARABIC LETTER REH -->
-        <Key
-            latin:keyLabel="ر" />
-        <!-- \u0630: ARABIC LETTER THAL -->
-        <Key
-            latin:keyLabel="ذ" />
-        <!-- \u0649: ARABIC LETTER ALEF MAKSURA -->
-        <Key
-            latin:keyLabel="ى" />
-        <!-- \u0629: ARABIC LETTER TEH MARBUTA -->
-        <Key
-            latin:keyLabel="ة" />
-        <!-- \u0648: ARABIC LETTER WAW -->
-        <Key
-            latin:keyLabel="و" />
-        <!-- \u0632: ARABIC LETTER ZAIN
-             \u0698: ARABIC LETTER JEH -->
-        <Key
-            latin:keyLabel="ز"
-            latin:moreKeys="ژ" />
-        <!-- \u0638: ARABIC LETTER ZAH -->
-        <Key
-            latin:keyLabel="ظ" />
-        <!-- \u062f: ARABIC LETTER DAL -->
-        <Key
-            latin:keyLabel="د" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_azerty.xml b/java/res/xml-sw600dp/kbd_rows_azerty.xml
deleted file mode 100644
index 8ae7455..0000000
--- a/java/res/xml-sw600dp/kbd_rows_azerty.xml
+++ /dev/null
@@ -1,150 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="8.5%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.5%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q"
-            latin:keyXPos="5.0%p" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="m" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.5%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="\'" />
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="," />
-                <Key
-                    latin:keyLabel="." />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="!"
-                    latin:moreKeys="!" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="\?"
-                    latin:moreKeys="\?" />
-            </default>
-        </switch>
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_smiley" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_hebrew.xml b/java/res/xml-sw600dp/kbd_rows_hebrew.xml
deleted file mode 100644
index a8adbd3..0000000
--- a/java/res/xml-sw600dp/kbd_rows_hebrew.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/kbd_row4_apostrophe_dash" />
-        <Key
-            latin:keyLabel="ק" />
-        <Key
-            latin:keyLabel="ר" />
-        <Key
-            latin:keyLabel="א" />
-        <Key
-            latin:keyLabel="ט" />
-        <Key
-            latin:keyLabel="ו" />
-        <Key
-            latin:keyLabel="ן" />
-        <Key
-            latin:keyLabel="ם" />
-        <Key
-            latin:keyLabel="פ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-12.000%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="ש"
-            latin:keyXPos="4.500%p" />
-        <Key
-            latin:keyLabel="ד" />
-        <Key
-            latin:keyLabel="ג"
-            latin:moreKeys="ג׳" />
-        <Key
-            latin:keyLabel="כ" />
-        <Key
-            latin:keyLabel="ע" />
-        <Key
-            latin:keyLabel="י"
-            latin:moreKeys="ײַ" />
-        <Key
-            latin:keyLabel="ח"
-            latin:moreKeys="ח׳" />
-        <Key
-            latin:keyLabel="ל" />
-        <Key
-            latin:keyLabel="ך" />
-        <Key
-            latin:keyLabel="ף" />
-    </Row>
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <!-- kbd_row3_smiley -->
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="\@"
-                    latin:keyWidth="10.0%p" />
-            </case>
-            <case
-                latin:mode="url"
-            >
-                <Key
-                    latin:keyLabel="-"
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="_"
-                    latin:moreKeys="_"
-                    latin:keyWidth="10.0%p" />
-            </case>
-            <case
-                latin:imeAction="actionSearch"
-            >
-                <Key
-                    latin:keyLabel=":"
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="+"
-                    latin:moreKeys="+"
-                    latin:keyWidth="10.0%p" />
-            </case>
-            <default>
-                <Key
-                    latin:keyStyle="smileyKeyStyle"
-                    latin:keyWidth="10.0%p" />
-            </default>
-        </switch>
-        <Key
-            latin:keyLabel="ז"
-            latin:moreKeys="ז׳" />
-        <Key
-            latin:keyLabel="ס" />
-        <Key
-            latin:keyLabel="ב" />
-        <Key
-            latin:keyLabel="ה" />
-        <Key
-            latin:keyLabel="נ" />
-        <Key
-            latin:keyLabel="מ" />
-        <Key
-            latin:keyLabel="צ"
-            latin:moreKeys="צ׳" />
-        <Key
-            latin:keyLabel="ת"
-            latin:moreKeys="ת׳" />
-        <Key
-            latin:keyLabel="ץ"
-            latin:moreKeys="ץ׳" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-10.400%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_qwerty.xml b/java/res/xml-sw600dp/kbd_rows_qwerty.xml
deleted file mode 100644
index a2d26b3..0000000
--- a/java/res/xml-sw600dp/kbd_rows_qwerty.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_qwertz.xml b/java/res/xml-sw600dp/kbd_rows_qwertz.xml
deleted file mode 100644
index 98667e0..0000000
--- a/java/res/xml-sw600dp/kbd_rows_qwertz.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="," />
-                <Key
-                    latin:keyLabel="." />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="!"
-                    latin:moreKeys="!" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="\?"
-                    latin:moreKeys="\?" />
-            </default>
-        </switch>
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_smiley" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_russian.xml b/java/res/xml-sw600dp/kbd_rows_russian.xml
deleted file mode 100644
index cc9ad3a..0000000
--- a/java/res/xml-sw600dp/kbd_rows_russian.xml
+++ /dev/null
@@ -1,140 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="7.60%p"
-    >
-        <Key
-            latin:keyLabel="й" />
-        <Key
-            latin:keyLabel="ц" />
-        <Key
-            latin:keyLabel="у" />
-        <Key
-            latin:keyLabel="к" />
-        <Key
-            latin:keyLabel="е"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
-        <Key
-            latin:keyLabel="н" />
-        <Key
-            latin:keyLabel="г" />
-        <Key
-            latin:keyLabel="ш" />
-        <Key
-            latin:keyLabel="щ" />
-        <Key
-            latin:keyLabel="з" />
-        <Key
-            latin:keyLabel="х" />
-        <Key
-            latin:keyLabel="ъ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.60%p"
-    >
-        <Key
-            latin:keyLabel="ф"
-            latin:keyXPos="2.25%p" />
-        <Key
-            latin:keyLabel="ы" />
-        <Key
-            latin:keyLabel="в" />
-        <Key
-            latin:keyLabel="а" />
-        <Key
-            latin:keyLabel="п" />
-        <Key
-            latin:keyLabel="р" />
-        <Key
-            latin:keyLabel="о" />
-        <Key
-            latin:keyLabel="л" />
-        <Key
-            latin:keyLabel="д" />
-        <Key
-            latin:keyLabel="ж" />
-        <Key
-            latin:keyLabel="э" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.60%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle" />
-        <Key
-            latin:keyLabel="я" />
-        <Key
-            latin:keyLabel="ч" />
-        <Key
-            latin:keyLabel="с" />
-        <Key
-            latin:keyLabel="м" />
-        <Key
-            latin:keyLabel="и" />
-        <Key
-            latin:keyLabel="т" />
-        <Key
-            latin:keyLabel="ь" />
-        <Key
-            latin:keyLabel="б" />
-        <Key
-            latin:keyLabel="ю" />
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="," />
-                <Key
-                    latin:keyLabel="." />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="!"
-                    latin:moreKeys="!" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="\?"
-                    latin:moreKeys="\?" />
-            </default>
-        </switch>
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_smiley" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_scandinavian.xml b/java/res/xml-sw600dp/kbd_rows_scandinavian.xml
deleted file mode 100644
index 19fb521..0000000
--- a/java/res/xml-sw600dp/kbd_rows_scandinavian.xml
+++ /dev/null
@@ -1,140 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="7.9%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyLabel="å" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.9%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:keyXPos="3.5%p"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_scandinavia_row2_10"
-            latin:moreKeys="@string/more_keys_for_scandinavia_row2_10" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_scandinavia_row2_11"
-            latin:moreKeys="@string/more_keys_for_scandinavia_row2_11" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.9%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Spacer
-            latin:keyWidth="4.35%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <Spacer
-            latin:keyWidth="4.35%p" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_smiley" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_serbian.xml b/java/res/xml-sw600dp/kbd_rows_serbian.xml
deleted file mode 100644
index db7560c..0000000
--- a/java/res/xml-sw600dp/kbd_rows_serbian.xml
+++ /dev/null
@@ -1,118 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="8.0%p"
-    >
-        <Key
-            latin:keyLabel="љ"
-            latin:keyXPos="2.15%p" />
-        <Key
-            latin:keyLabel="њ" />
-        <Key
-            latin:keyLabel="е" />
-        <Key
-            latin:keyLabel="р" />
-        <Key
-            latin:keyLabel="т" />
-        <Key
-            latin:keyLabel="з" />
-        <Key
-            latin:keyLabel="у" />
-        <Key
-            latin:keyLabel="и" />
-        <Key
-            latin:keyLabel="о" />
-        <Key
-            latin:keyLabel="п" />
-        <Key
-            latin:keyLabel="ш" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.9%p"
-    >
-        <Key
-            latin:keyLabel="а" />
-        <Key
-            latin:keyLabel="с" />
-        <Key
-            latin:keyLabel="д" />
-        <Key
-            latin:keyLabel="ф" />
-        <Key
-            latin:keyLabel="г" />
-        <Key
-            latin:keyLabel="х" />
-        <Key
-            latin:keyLabel="ј" />
-        <Key
-            latin:keyLabel="к" />
-        <Key
-            latin:keyLabel="л" />
-        <Key
-            latin:keyLabel="ч" />
-        <Key
-            latin:keyLabel="ћ" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.5%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="8.0%p" />
-        <Key
-            latin:keyLabel="ѕ" />
-        <Key
-            latin:keyLabel="џ" />
-        <Key
-            latin:keyLabel="ц" />
-        <Key
-            latin:keyLabel="в" />
-        <Key
-            latin:keyLabel="б" />
-        <Key
-            latin:keyLabel="н" />
-        <Key
-            latin:keyLabel="м" />
-        <Key
-            latin:keyLabel="ђ" />
-        <Key
-            latin:keyLabel="ж" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_smiley" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_spanish.xml b/java/res/xml-sw600dp/kbd_rows_spanish.xml
deleted file mode 100644
index 8506af6..0000000
--- a/java/res/xml-sw600dp/kbd_rows_spanish.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <Row
-        latin:keyWidth="8.5%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a"
-            latin:keyXPos="5.0%p" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="ñ" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_symbols.xml b/java/res/xml-sw600dp/kbd_rows_symbols.xml
deleted file mode 100644
index bb48fe7..0000000
--- a/java/res/xml-sw600dp/kbd_rows_symbols.xml
+++ /dev/null
@@ -1,172 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_currency_key_styles" />
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_1"
-            latin:moreKeys="@string/more_keys_for_symbols_1" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_2"
-            latin:moreKeys="@string/more_keys_for_symbols_2" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_3"
-            latin:moreKeys="@string/more_keys_for_symbols_3" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_4"
-            latin:moreKeys="@string/more_keys_for_symbols_4" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_5"
-            latin:moreKeys="@string/more_keys_for_symbols_5" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_6"
-            latin:moreKeys="@string/more_keys_for_symbols_6" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_7"
-            latin:moreKeys="@string/more_keys_for_symbols_7" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_8"
-            latin:moreKeys="@string/more_keys_for_symbols_8" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_9"
-            latin:moreKeys="@string/more_keys_for_symbols_9" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_0"
-            latin:moreKeys="@string/more_keys_for_symbols_0" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="#"
-            latin:keyXPos="4.5%p" />
-        <Key
-            latin:keyStyle="currencyKeyStyle" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_percent"
-            latin:moreKeys="@string/more_keys_for_symbols_percent" />
-        <Key
-            latin:keyLabel="&amp;" />
-        <Key
-            latin:keyLabel="*"
-            latin:moreKeys="@string/more_keys_for_star" />
-        <Key
-            latin:keyLabel="-"
-            latin:moreKeys="_,–,—" />
-        <Key
-            latin:keyLabel="+"
-            latin:moreKeys="@string/more_keys_for_plus" />
-        <Key
-            latin:keyLabel="("
-            latin:moreKeys="[,{,&lt;" />
-        <Key
-            latin:keyLabel=")"
-            latin:moreKeys="],},&gt;" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Key
-            latin:keyLabel="&lt;"
-            latin:moreKeys="≤,«,‹" />
-        <Key
-            latin:keyLabel="&gt;"
-            latin:moreKeys="≥,»,›" />
-        <Key
-            latin:keyLabel="="
-            latin:moreKeys="≠,≈" />
-        <switch>
-            <case
-                latin:mode="url"
-            >
-                <Key
-                    latin:keyLabel="\'"
-                    latin:moreKeys="‘,’,‚,‛" />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=":" />
-            </default>
-        </switch>
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_semicolon"
-            latin:moreKeys="@string/more_keys_for_symbols_semicolon" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_comma"
-            latin:moreKeys="@string/more_keys_for_comma" />
-        <Key
-            latin:keyLabel="." />
-        <Key
-            latin:keyLabel="!"
-            latin:moreKeys="¡" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_question"
-            latin:moreKeys="@string/more_keys_for_symbols_question" />
-        <Key
-            latin:keyLabel="/"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="13.0%p" />
-        <Key
-            latin:keyStyle="tabKeyStyle" />
-        <Key
-            latin:keyLabel="\@" />
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="30.750%p"
-            latin:keyWidth="39.750%p" />
-        <!-- Note: DroidSans doesn't have double-high-reversed-quotation '\u201f' glyph. -->
-        <!-- latin:moreKeys="“,”,„,‟,«,»,‘,’,‚,‛" -->
-        <Key
-            latin:keyLabel="&quot;"
-            latin:moreKeys="“,”,«,»,‘,’,‚,‛" />
-        <Key
-            latin:keyLabel="_" />
-        <Spacer
-            latin:keyXPos="-10.00%p"
-            latin:keyWidth="0%p" />
-        <include
-            latin:keyboardLayout="@xml/kbd_qwerty_f2" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml b/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml
deleted file mode 100644
index 8e47515..0000000
--- a/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml
+++ /dev/null
@@ -1,132 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_currency_key_styles" />
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="~" />
-        <Key
-            latin:keyLabel="`" />
-        <Key
-            latin:keyLabel="|" />
-        <Key
-            latin:keyLabel="•"
-            latin:moreKeys="@string/more_keys_for_bullet" />
-        <Key
-            latin:keyLabel="√" />
-        <Key
-            latin:keyLabel="π"
-            latin:moreKeys="Π" />
-        <Key
-            latin:keyLabel="÷" />
-        <Key
-            latin:keyLabel="×" />
-        <Key
-            latin:keyLabel="§"
-            latin:moreKeys="¶" />
-        <Key
-            latin:keyLabel="Δ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-10.0%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyLabel="£"
-            latin:keyXPos="4.5%p" />
-        <Key
-            latin:keyStyle="moreCurrency1KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency2KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency3KeyStyle" />
-        <Key
-            latin:keyLabel="^"
-            latin:moreKeys="↑,↓,←,→" />
-        <Key
-            latin:keyLabel="°"
-            latin:moreKeys="′,″" />
-        <Key
-            latin:keyLabel="±"
-            latin:moreKeys="∞" />
-        <Key
-            latin:keyLabel="{" />
-        <Key
-            latin:keyLabel="}" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-14.6%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Key
-            latin:keyLabel="\\" />
-        <Key
-            latin:keyLabel="©" />
-        <Key
-            latin:keyLabel="®" />
-        <Key
-            latin:keyLabel="™" />
-        <Key
-            latin:keyLabel="℅" />
-        <Key
-            latin:keyLabel="[" />
-        <Key
-            latin:keyLabel="]" />
-        <Key
-            latin:keyLabel="¡" />
-        <Key
-            latin:keyLabel="¿" />
-    </Row>
-    <Row
-        latin:keyWidth="8.9%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="13.0%p" />
-        <Key
-            latin:keyStyle="tabKeyStyle" />
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="30.750%p"
-            latin:keyWidth="39.750%p" />
-        <Spacer
-            latin:keyXPos="-10.00%p"
-            latin:keyWidth="0%p" />
-        <include
-            latin:keyboardLayout="@xml/kbd_qwerty_f2" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_spanish_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_spanish_symbols.xml
index e29d9ab..66254de 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_spanish_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_spanish_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_spanish_symbols_shift.xml
index e29d9ab..3c5ed5e 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_spanish_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_thai.xml
similarity index 77%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_thai.xml
index e29d9ab..b75980f 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/kbd_thai.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="3.20%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/key_azerty_quote.xml
similarity index 75%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/key_azerty_quote.xml
index e29d9ab..0e4a8ec 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/key_azerty_quote.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,12 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
+    <Key
+        latin:keyLabel="\'"
+        latin:keyHintLabel=":"
+        latin:moreKeys=":"
+        latin:keyStyle="hasShiftedLetterHintStyle" />
+</merge>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw600dp/key_greek_semicolon.xml
similarity index 76%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw600dp/key_greek_semicolon.xml
index e29d9ab..3f09419 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp/key_greek_semicolon.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,12 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
+    <Key
+        latin:keyLabel=";"
+        latin:keyHintLabel=":"
+        latin:moreKeys=":"
+        latin:keyStyle="hasShiftedLetterHintStyle" />
+</merge>
diff --git a/java/res/xml-sw600dp/key_shortcut.xml b/java/res/xml-sw600dp/key_shortcut.xml
new file mode 100644
index 0000000..d4c45ad
--- /dev/null
+++ b/java/res/xml-sw600dp/key_shortcut.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:shortcutKeyEnabled="true"
+            latin:clobberSettingsKey="false"
+        >
+            <Key
+                latin:keyStyle="shortcutKeyStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="@string/settings_as_more_key"
+                latin:keyWidth="fillBoth" />
+        </case>
+        <case
+            latin:shortcutKeyEnabled="true"
+            latin:clobberSettingsKey="true"
+        >
+            <Key
+                latin:keyStyle="shortcutKeyStyle"
+                latin:keyWidth="fillBoth" />
+        </case>
+        <case
+            latin:shortcutKeyEnabled="false"
+            latin:clobberSettingsKey="false"
+        >
+            <Key
+                latin:keyStyle="settingsKeyStyle"
+                latin:keyWidth="fillBoth" />
+        </case>
+        <!-- shortcutKeyEnabled="false" clobberSettingsKey="true" -->
+        <default>
+            <Spacer />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_row3_smiley.xml b/java/res/xml-sw600dp/key_smiley.xml
similarity index 71%
rename from java/res/xml-sw600dp/kbd_row3_smiley.xml
rename to java/res/xml-sw600dp/key_smiley.xml
index f9b647c..3430d78 100644
--- a/java/res/xml-sw600dp/kbd_row3_smiley.xml
+++ b/java/res/xml-sw600dp/key_smiley.xml
@@ -26,37 +26,29 @@
             latin:mode="email"
         >
             <Key
-                latin:keyLabel="\@"
-                latin:keyXPos="-8.9%p"
-                latin:keyWidth="fillBoth" />
+                latin:keyLabel="\@" />
         </case>
         <case
             latin:mode="url"
         >
             <Key
                 latin:keyLabel="-"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="_"
                 latin:moreKeys="_"
-                latin:keyXPos="-8.9%p"
-                latin:keyWidth="fillBoth" />
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </case>
         <case
             latin:imeAction="actionSearch"
         >
             <Key
                 latin:keyLabel=":"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="+"
                 latin:moreKeys="+"
-                latin:keyXPos="-8.9%p"
-                latin:keyWidth="fillBoth" />
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </case>
         <default>
             <Key
-                latin:keyStyle="smileyKeyStyle"
-                latin:keyXPos="-8.9%p"
-                latin:keyWidth="fillBoth" />
+                latin:keyStyle="smileyKeyStyle" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
new file mode 100644
index 0000000..77c0efd
--- /dev/null
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="hasShiftedLetterHintStyle"
+                latin:keyLabelFlags="hasShiftedLetterHint|shiftedLetterActivated" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="hasShiftedLetterHintStyle"
+                latin:keyLabelFlags="hasShiftedLetterHint" />
+        </default>
+    </switch>
+    <!-- Functional key styles -->
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetAutomaticShifted"
+        >
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKeyShifted"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOff" />
+        </case>
+        <case
+            latin:keyboardSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKeyShifted"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOn" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKey"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOff" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="deleteKeyStyle"
+        latin:code="@integer/key_delete"
+        latin:keyIcon="iconDeleteKey"
+        latin:keyActionFlags="isRepeatable|noKeyPreview"
+        latin:backgroundType="functional" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_enter" />
+    <key-style
+        latin:styleName="spaceKeyStyle"
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview" />
+    <!-- U+200C: ZERO WIDTH NON-JOINER
+         U+200D: ZERO WIDTH JOINER -->
+    <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:code="0x200C"
+        latin:keyIcon="iconZwnjKey"
+        latin:moreKeys="\@icon/zwjKey|&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        latin:keyActionFlags="noKeyPreview" />
+    <key-style
+        latin:styleName="smileyKeyStyle"
+        latin:keyLabel=":-)"
+        latin:keyOutputText=":-) "
+        latin:keyLabelFlags="hasPopupHint|preserveCase"
+        latin:moreKeys="@string/more_keys_for_smiley" />
+    <key-style
+        latin:styleName="shortcutKeyStyle"
+        latin:code="@integer/key_shortcut"
+        latin:keyIcon="iconShortcutKey"
+        latin:keyIconDisabled="iconDisabledShortcutKey"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="settingsKeyStyle"
+        latin:code="@integer/key_settings"
+        latin:keyIcon="iconSettingsKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:backgroundType="functional" />
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:navigatePrevious="true"
+        >
+            <key-style
+                latin:styleName="tabKeyStyle"
+                latin:code="@integer/key_action_previous"
+                latin:keyIcon="iconTabKey"
+                latin:keyIconPreview="iconPreviewTabKey"
+                latin:backgroundType="functional" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="tabKeyStyle"
+                latin:code="@integer/key_tab"
+                latin:keyIcon="iconTabKey"
+                latin:keyIconPreview="iconPreviewTabKey"
+                latin:backgroundType="functional" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="toSymbolKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
+        latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="toAlphaKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
+        latin:keyLabel="@string/label_to_alpha_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="toMoreSymbolKeyStyle"
+        latin:code="@integer/key_shift"
+        latin:keyLabel="@string/label_to_more_symbol_for_tablet_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="backFromMoreSymbolKeyStyle"
+        latin:code="@integer/key_shift"
+        latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="comKeyStyle"
+        latin:keyLabel="@string/keylabel_for_popular_domain"
+        latin:keyLabelFlags="fontNormal|hasPopupHint|preserveCase"
+        latin:keyOutputText="@string/keylabel_for_popular_domain"
+        latin:moreKeys="@string/more_keys_for_popular_domain" />
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml b/java/res/xml-sw600dp/keys_apostrophe_dash.xml
similarity index 85%
rename from java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
rename to java/res/xml-sw600dp/keys_apostrophe_dash.xml
index 9536e81..a53c1e4 100644
--- a/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
+++ b/java/res/xml-sw600dp/keys_apostrophe_dash.xml
@@ -33,16 +33,16 @@
         >
             <Key
                 latin:keyLabel="/"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel=":"
-                latin:moreKeys=":" />
+                latin:moreKeys=":"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </case>
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_apostrophe"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_apostrophe"
-                latin:moreKeys="@string/more_keys_for_apostrophe" />
+                latin:moreKeys="@string/more_keys_for_apostrophe"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
     <switch>
@@ -55,9 +55,9 @@
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_dash"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_dash"
-                latin:moreKeys="@string/more_keys_for_dash" />
+                latin:moreKeys="@string/more_keys_for_dash"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/kbd_row3_comma_period.xml b/java/res/xml-sw600dp/keys_comma_period.xml
similarity index 66%
rename from java/res/xml-sw600dp/kbd_row3_comma_period.xml
rename to java/res/xml-sw600dp/keys_comma_period.xml
index b844430..f5f307b 100644
--- a/java/res/xml-sw600dp/kbd_row3_comma_period.xml
+++ b/java/res/xml-sw600dp/keys_comma_period.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -32,15 +32,15 @@
         </case>
         <default>
             <Key
-                latin:keyLabel=","
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel="!"
-                latin:moreKeys="!" />
+                latin:keyLabel="@string/keylabel_for_tablet_comma"
+                latin:keyHintLabel="@string/keyhintlabel_for_tablet_comma"
+                latin:moreKeys="@string/more_keys_for_tablet_comma"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
             <Key
                 latin:keyLabel="."
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel="\?"
-                latin:moreKeys="\?" />
+                latin:keyHintLabel="@string/keyhintlabel_for_tablet_period"
+                latin:moreKeys="@string/more_keys_for_tablet_period"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/kbd_qwerty_row4.xml b/java/res/xml-sw600dp/row_qwerty4.xml
similarity index 67%
rename from java/res/xml-sw600dp/kbd_qwerty_row4.xml
rename to java/res/xml-sw600dp/row_qwerty4.xml
index ef02922..eec35b0 100644
--- a/java/res/xml-sw600dp/kbd_qwerty_row4.xml
+++ b/java/res/xml-sw600dp/row_qwerty4.xml
@@ -45,32 +45,46 @@
             <default>
                 <Key
                     latin:keyLabel="/"
-                    latin:keyLabelOption="hasUppercaseLetter"
                     latin:keyHintLabel="\@"
-                    latin:moreKeys="\@" />
+                    latin:moreKeys="\@"
+                    latin:keyStyle="hasShiftedLetterHintStyle" />
             </default>
         </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="30.750%p"
-            latin:keyWidth="39.750%p" />
+        <switch>
+            <case
+                latin:languageCode="fa"
+            >
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="30.750%p"
+                    latin:keyWidth="30.850%p" />
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <default>
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="30.750%p"
+                    latin:keyWidth="39.750%p" />
+            </default>
+        </switch>
         <switch>
             <case
                 latin:languageCode="iw"
             >
                 <include
-                    latin:keyboardLayout="@xml/kbd_row3_comma_period" />
+                    latin:keyboardLayout="@xml/keys_comma_period" />
             </case>
             <!-- not languageCode="iw" -->
             <default>
                 <include
-                    latin:keyboardLayout="@xml/kbd_row4_apostrophe_dash" />
+                    latin:keyboardLayout="@xml/keys_apostrophe_dash" />
             </default>
         </switch>
         <Spacer
             latin:keyXPos="-10.00%p"
             latin:keyWidth="0%p" />
         <include
-            latin:keyboardLayout="@xml/kbd_qwerty_f2" />
+            latin:keyboardLayout="@xml/key_shortcut" />
     </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic1.xml b/java/res/xml-sw600dp/rowkeys_arabic1.xml
new file mode 100644
index 0000000..44fdc67
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_arabic1.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0636: "ض" ARABIC LETTER DAD -->
+    <Key
+        latin:keyLabel="&#x0636;" />
+    <!-- U+0635: "ص" ARABIC LETTER SAD -->
+    <Key
+        latin:keyLabel="&#x0635;" />
+    <!-- U+062B: "ث" ARABIC LETTER THEH -->
+    <Key
+        latin:keyLabel="&#x062B;" />
+    <!-- U+0642: "ق" ARABIC LETTER QAF
+         U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
+    <Key
+        latin:keyLabel="&#x0642;"
+        latin:moreKeys="&#x06A8;" />
+    <!-- U+0641: "ف" ARABIC LETTER FEH
+         U+06A4: "ڤ" ARABIC LETTER VEH
+         U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
+         U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06A2 ARABIC LETTER FEH WITH DOT MOVED BELOW -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
+    <Key
+        latin:keyLabel="&#x0641;"
+        latin:moreKeys="&#x06A4;,&#x06A2;,&#x06A5;" />
+    <!-- U+063A: "غ" ARABIC LETTER GHAIN -->
+    <Key
+        latin:keyLabel="&#x063A;" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN -->
+    <Key
+        latin:keyLabel="&#x0639;" />
+    <!-- U+0647: "ه" ARABIC LETTER HEH
+         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
+         U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER -->
+    <Key
+        latin:keyLabel="&#x0647;"
+        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;" />
+    <!-- U+062E: "خ" ARABIC LETTER KHAH -->
+    <Key
+        latin:keyLabel="&#x062E;" />
+    <!-- U+062D: "ح" ARABIC LETTER HAH -->
+    <Key
+        latin:keyLabel="&#x062D;" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM
+         U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x062C;"
+        latin:moreKeys="&#x0686;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic2.xml b/java/res/xml-sw600dp/rowkeys_arabic2.xml
new file mode 100644
index 0000000..3eba2fb
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_arabic2.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0634: "ش" ARABIC LETTER SHEEN
+         U+069C: "ڜ" ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+069C ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
+    <Key
+        latin:keyLabel="&#x0634;"
+        latin:moreKeys="&#x069C;" />
+    <!-- U+0633: "س" ARABIC LETTER SEEN -->
+    <Key
+        latin:keyLabel="&#x0633;" />
+    <!-- U+064A: "ي" ARABIC LETTER YEH
+         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
+         U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
+    <Key
+        latin:keyLabel="&#x064A;"
+        latin:moreKeys="&#x0626;,&#x0649;" />
+    <!-- U+0628: "ب" ARABIC LETTER BEH
+         U+067E: "پ" ARABIC LETTER PEH -->
+    <Key
+        latin:keyLabel="&#x0628;"
+        latin:moreKeys="&#x067E;" />
+    <!-- U+0644: "ل" ARABIC LETTER LAM
+         U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
+         U+0627: "ا" ARABIC LETTER ALEF
+         U+FEF7: "ﻷ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
+         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
+         U+FEF9: "ﻹ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
+         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
+         U+FEF5: "ﻵ" ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0644;"
+        latin:moreKeys="&#xFEFB;|&#x0644;&#x0627;,&#xFEF7;|&#x0644;&#x0623;,&#xFEF9;|&#x0644;&#x0625;,&#xFEF5;|&#x0644;&#x0622;" />
+    <!-- U+0627: "ا" ARABIC LETTER ALEF
+         U+0621: "ء" ARABIC LETTER HAMZA
+         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
+         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
+         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0627;"
+        latin:moreKeys="&#x0621;,&#x0671;,&#x0623;,&#x0625;,&#x0622;" />
+    <!-- U+062A: "ت" ARABIC LETTER TEH -->
+    <Key
+        latin:keyLabel="&#x062A;" />
+    <!-- U+0646: "ن" ARABIC LETTER NOON -->
+    <Key
+        latin:keyLabel="&#x0646;" />
+    <!-- U+0645: "م" ARABIC LETTER MEEM -->
+    <Key
+        latin:keyLabel="&#x0645;" />
+    <!-- U+0643: "ك" ARABIC LETTER KAF
+         U+06AF: "گ" ARABIC LETTER GAF
+         U+06A9: "ک" ARABIC LETTER KEHEH -->
+    <Key
+        latin:keyLabel="&#x0643;"
+        latin:moreKeys="&#x06AF;,&#x06A9;" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <Key
+        latin:keyLabel="&#x0637;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic3.xml b/java/res/xml-sw600dp/rowkeys_arabic3.xml
new file mode 100644
index 0000000..911550f
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_arabic3.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0626;" />
+    <!-- U+0621: "ء" ARABIC LETTER HAMZA -->
+    <Key
+        latin:keyLabel="&#x0621;" />
+    <!-- U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0624;" />
+    <!-- U+0631: "ر" ARABIC LETTER REH -->
+    <Key
+        latin:keyLabel="&#x0631;" />
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <Key
+        latin:keyLabel="&#x0630;" />
+    <!-- U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
+    <Key
+        latin:keyLabel="&#x0649;" />
+    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <Key
+        latin:keyLabel="&#x0629;" />
+    <!-- U+0648: "و" ARABIC LETTER WAW -->
+    <Key
+        latin:keyLabel="&#x0648;" />
+    <!-- U+0632: "ز" ARABIC LETTER ZAIN
+         U+0698: "ژ" ARABIC LETTER JEH -->
+    <Key
+        latin:keyLabel="&#x0632;"
+        latin:moreKeys="&#x0698;" />
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <Key
+        latin:keyLabel="&#x0638;" />
+    <!-- U+062F: "د" ARABIC LETTER DAL -->
+    <Key
+        latin:keyLabel="&#x062F;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi1.xml b/java/res/xml-sw600dp/rowkeys_farsi1.xml
new file mode 100644
index 0000000..ab260a4
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_farsi1.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0636: "ض" ARABIC LETTER DAD -->
+    <Key
+        latin:keyLabel="&#x0636;" />
+    <!-- U+0635: "ص" ARABIC LETTER SAD -->
+    <Key
+        latin:keyLabel="&#x0635;" />
+    <!-- U+062B: "ث" ARABIC LETTER THEH -->
+    <Key
+        latin:keyLabel="&#x062B;" />
+    <!-- U+0642: "ق" ARABIC LETTER QAF -->
+    <Key
+        latin:keyLabel="&#x0642;" />
+    <!-- U+0641: "ف" ARABIC LETTER FEH -->
+    <Key
+        latin:keyLabel="&#x0641;" />
+    <!-- U+063A: "غ" ARABIC LETTER GHAIN -->
+    <Key
+        latin:keyLabel="&#x063A;" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN -->
+    <Key
+        latin:keyLabel="&#x0639;" />
+    <!-- U+0647: "ه" ARABIC LETTER HEH
+         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
+         U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
+         U+06C0: "ۀ" ARABIC LETTER HEH WITH YEH ABOVE
+         U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06C0 ARABIC LETTER HEH WITH YEH ABOVE -->
+    <Key
+        latin:keyLabel="&#x0647;"
+        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;,&#x06C0;,&#x0629;,%" />
+    <!-- U+062E: "خ" ARABIC LETTER KHAH -->
+    <Key
+        latin:keyLabel="&#x062E;" />
+    <!-- U+062D: "ح" ARABIC LETTER HAH -->
+    <Key
+        latin:keyLabel="&#x062D;" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM -->
+    <Key
+        latin:keyLabel="&#x062C;" />
+    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x0686;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi2.xml b/java/res/xml-sw600dp/rowkeys_farsi2.xml
new file mode 100644
index 0000000..98e0f21
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_farsi2.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0634: "ش" ARABIC LETTER SHEEN -->
+    <Key
+        latin:keyLabel="&#x0634;" />
+    <!-- U+0633: "س" ARABIC LETTER SEEN -->
+    <Key
+        latin:keyLabel="&#x0633;" />
+    <!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
+         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
+         U+064A: "ي" ARABIC LETTER YEH -->
+    <Key
+        latin:keyLabel="&#x06CC;"
+        latin:moreKeys="&#x0626;,&#x064A;" />
+    <!-- U+0628: "ب" ARABIC LETTER BEH -->
+    <Key
+        latin:keyLabel="&#x0628;" />
+    <!-- U+0644: "ل" ARABIC LETTER LAM -->
+    <Key
+        latin:keyLabel="&#x0644;" />
+    <!-- U+0627: "ا" ARABIC LETTER ALEF
+         U+0621: "ء" ARABIC LETTER HAMZA
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
+         U+0672: "ٲ" ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE
+         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
+         U+0673: "ٳ" ARABIC LETTER ALEF WITH WAVY HAMZA BELOW-->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+0672 ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+0673 ARABIC LETTER ALEF WITH WAVY HAMZA BELOW -->
+    <Key
+        latin:keyLabel="&#x0627;"
+        latin:moreKeys="&#x0621;,&#x0622;,&#x0672;,&#x0671;,&#x0673;" />
+    <!-- U+062A: "ت" ARABIC LETTER TEH -->
+    <Key
+        latin:keyLabel="&#x062A;" />
+    <!-- U+0646: "ن" ARABIC LETTER NOON -->
+    <Key
+        latin:keyLabel="&#x0646;" />
+    <!-- U+0645: "م" ARABIC LETTER MEEM -->
+    <Key
+        latin:keyLabel="&#x0645;" />
+    <!-- U+06A9: "ک" ARABIC LETTER KEHEH
+         U+0643: "ك" ARABIC LETTER KAF -->
+    <Key
+        latin:keyLabel="&#x06A9;"
+        latin:moreKeys="&#x0643;" />
+    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
+    <Key
+        latin:keyLabel="&#x06AF;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi3.xml b/java/res/xml-sw600dp/rowkeys_farsi3.xml
new file mode 100644
index 0000000..c80c14a
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_farsi3.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <Key
+        latin:keyLabel="&#x0638;" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <Key
+        latin:keyLabel="&#x0637;" />
+    <!-- U+0632: "ز" ARABIC LETTER ZAIN
+         U+0698: "ژ" ARABIC LETTER JEH -->
+    <Key
+        latin:keyLabel="&#x0632;"
+        latin:moreKeys="&#x0698;" />
+    <!-- U+0631: "ر" ARABIC LETTER REH -->
+    <Key
+        latin:keyLabel="&#x0631;" />
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <Key
+        latin:keyLabel="&#x0630;" />
+    <!-- U+062F: "د" ARABIC LETTER DAL -->
+    <Key
+        latin:keyLabel="&#x062F;" />
+    <!-- U+067E: "پ" ARABIC LETTER PEH -->
+    <Key
+        latin:keyLabel="&#x067E;" />
+    <!-- U+0648: "و" ARABIC LETTER WAW
+         U+0676: "ٶ" ARABIC LETTER HIGH HAMZA WAW -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+0676 ARABIC LETTER HIGH HAMZA WAW -->
+    <Key
+        latin:keyLabel="&#x0648;"
+        latin:moreKeys="&#x0676;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols2.xml b/java/res/xml-sw600dp/rowkeys_symbols2.xml
new file mode 100644
index 0000000..e0121a3
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_symbols2.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="#" />
+    <Key
+        latin:keyStyle="currencyKeyStyle" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_percent"
+        latin:moreKeys="@string/more_keys_for_symbols_percent" />
+    <Key
+        latin:keyLabel="&amp;" />
+    <Key
+        latin:keyLabel="*"
+        latin:moreKeys="@string/more_keys_for_star" />
+    <!-- U+2013: "–" EN DASH
+         U+2014: "—" EM DASH -->
+    <Key
+        latin:keyLabel="-"
+        latin:moreKeys="_,&#x2013;,&#x2014;" />
+    <Key
+        latin:keyLabel="+"
+        latin:moreKeys="@string/more_keys_for_plus" />
+    <include
+        latin:keyboardLayout="@xml/keys_parentheses" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols3.xml b/java/res/xml-sw600dp/rowkeys_symbols3.xml
new file mode 100644
index 0000000..9293352
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_symbols3.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/keys_less_greater" />
+    <!-- U+2260: "≠" NOT EQUAL TO
+         U+2248: "≈" ALMOST EQUAL TO -->
+    <Key
+        latin:keyLabel="="
+        latin:moreKeys="&#x2260;,&#x2248;" />
+    <switch>
+        <case
+            latin:mode="url"
+        >
+            <Key
+                latin:keyLabel="\'" />
+        </case>
+        <default>
+            <Key
+                latin:keyLabel=":" />
+        </default>
+    </switch>
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_semicolon"
+        latin:moreKeys="@string/more_keys_for_symbols_semicolon" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_comma"
+        latin:moreKeys="@string/more_keys_for_comma" />
+    <Key
+        latin:keyLabel="." />
+    <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
+    <Key
+        latin:keyLabel="!"
+        latin:moreKeys="&#x00A1;" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_question"
+        latin:moreKeys="@string/more_keys_for_symbols_question" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols_shift1.xml b/java/res/xml-sw600dp/rowkeys_symbols_shift1.xml
new file mode 100644
index 0000000..356ee2f
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_symbols_shift1.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="~" />
+    <Key
+        latin:keyLabel="`" />
+    <Key
+        latin:keyLabel="|" />
+    <!-- U+2022: "•" BULLET -->
+    <Key
+        latin:keyLabel="&#x2022;"
+        latin:moreKeys="@string/more_keys_for_bullet" />
+    <!-- U+221A: "√" SQUARE ROOT -->
+    <Key
+        latin:keyLabel="&#x221A;" />
+    <!-- U+03C0: "π" GREEK SMALL LETTER PI
+         U+03A0: "Π" GREEK CAPITAL LETTER PI -->
+    <Key
+        latin:keyLabel="&#x03C0;"
+        latin:moreKeys="&#x03A0;" />
+    <!-- U+00F7: "÷" DIVISION SIGN -->
+    <Key
+        latin:keyLabel="&#x00F7;" />
+    <!-- U+00D7: "×" MULTIPLICATION SIGN -->
+    <Key
+        latin:keyLabel="&#x00D7;" />
+    <!-- U+00A7: "§" SECTION SIGN
+         U+00B6: "¶" PILCROW SIGN -->
+    <Key
+        latin:keyLabel="&#x00A7;"
+        latin:moreKeys="&#x00B6;" />
+    <!-- U+0394: "Δ" GREEK CAPITAL LETTER DELTA -->
+    <Key
+        latin:keyLabel="&#x0394;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols_shift2.xml b/java/res/xml-sw600dp/rowkeys_symbols_shift2.xml
new file mode 100644
index 0000000..2048b73
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_symbols_shift2.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyStyle="moreCurrency1KeyStyle" />
+    <Key
+        latin:keyStyle="moreCurrency2KeyStyle" />
+    <Key
+        latin:keyStyle="moreCurrency3KeyStyle" />
+    <Key
+        latin:keyStyle="moreCurrency4KeyStyle" />
+    <!-- U+2191: "↑" UPWARDS ARROW
+         U+2193: "↓" DOWNWARDS ARROW
+         U+2190: "←" LEFTWARDS ARROW
+         U+2192: "→" RIGHTWARDS ARROW -->
+    <Key
+        latin:keyLabel="^"
+        latin:moreKeys="&#x2191;,&#x2193;,&#x2190;,&#x2192;" />
+    <!-- U+00B0: "°" DEGREE SIGN
+         U+2032: "′" PRIME
+         U+2033: "″" DOUBLE PRIME -->
+    <Key
+        latin:keyLabel="&#x00B0;"
+        latin:moreKeys="&#x2032;,&#x2033;" />
+    <!-- U+00B1: "±" PLUS-MINUS SIGN
+         U+221E: "∞" INFINITY -->
+    <Key
+        latin:keyLabel="&#x00B1;"
+        latin:moreKeys="&#x221E;" />
+    <include
+        latin:keyboardLayout="@xml/keys_curly_brackets" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols_shift3.xml b/java/res/xml-sw600dp/rowkeys_symbols_shift3.xml
new file mode 100644
index 0000000..8bd8656
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_symbols_shift3.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="\\" />
+    <!-- U+00A9: "©" COPYRIGHT SIGN -->
+    <Key
+        latin:keyLabel="&#x00A9;" />
+    <!-- U+00AE: "®" REGISTERED SIGN -->
+    <Key
+        latin:keyLabel="&#x00AE;" />
+    <!-- U+2122: "™" TRADE MARK SIGN -->
+    <Key
+        latin:keyLabel="&#x2122;" />
+    <!-- U+2105: "℅" CARE OF -->
+    <Key
+        latin:keyLabel="&#x2105;" />
+    <include
+        latin:keyboardLayout="@xml/keys_square_brackets" />
+    <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
+    <Key
+        latin:keyLabel="&#x00A1;" />
+    <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+    <Key
+        latin:keyLabel="&#x00BF;" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai1.xml b/java/res/xml-sw600dp/rowkeys_thai1.xml
new file mode 100644
index 0000000..e49cb2b
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_thai1.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0E51: "๑" THAI DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x0E51;" />
+            <!-- U+0E52: "๒" THAI DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0E52;" />
+            <!-- U+0E53: "๓" THAI DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x0E53;" />
+            <!-- U+0E54: "๔" THAI DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0E54;" />
+            <!-- U+0E39: " ู" THAI CHARACTER SARA UU -->
+            <Key
+                latin:keyLabel="&#x0E39;" />
+            <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT -->
+            <Key
+                latin:keyLabel="&#x0E3F;" />
+            <!-- U+0E55: "๕" THAI DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0E55;" />
+            <!-- U+0E56: "๖" THAI DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x0E56;" />
+            <!-- U+0E57: "๗" THAI DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0E57;" />
+            <!-- U+0E58: "๘" THAI DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0E58;" />
+            <!-- U+0E59: "๙" THAI DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x0E59;" />
+        </case>
+        <default>
+            <!-- U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO -->
+            <Key
+                latin:keyLabel="&#x0E45;" />
+            <Key
+                latin:keyLabel="/" />
+            <!-- U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO -->
+            <Key
+                latin:keyLabel="&#x0E20;" />
+            <!-- U+0E16: "ถ" THAI CHARACTER THO THUNG -->
+            <Key
+                latin:keyLabel="&#x0E16;" />
+            <!-- U+0E38: " ุ" THAI CHARACTER SARA U -->
+            <Key
+                latin:keyLabel="&#x0E38;" />
+            <!-- U+0E36: " ึ" THAI CHARACTER SARA UE -->
+            <Key
+                latin:keyLabel="&#x0E36;" />
+            <!-- U+0E04: "ค" THAI CHARACTER KHO KHWAI -->
+            <Key
+                latin:keyLabel="&#x0E04;" />
+            <!-- U+0E15: "ต" THAI CHARACTER TO TAO -->
+            <Key
+                latin:keyLabel="&#x0E15;" />
+            <!-- U+0E08: "จ" THAI CHARACTER CHO CHAN -->
+            <Key
+                latin:keyLabel="&#x0E08;" />
+            <!-- U+0E02: "ข" THAI CHARACTER KHO KHAI -->
+            <Key
+                latin:keyLabel="&#x0E02;" />
+            <!-- U+0E0A: "ช" THAI CHARACTER CHO CHANG -->
+            <Key
+                latin:keyLabel="&#x0E0A;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai2.xml b/java/res/xml-sw600dp/rowkeys_thai2.xml
new file mode 100644
index 0000000..0edae1c
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_thai2.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0E50: "๐" THAI DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0E50;" />
+            <Key
+                latin:keyLabel="&quot;" />
+            <!-- U+0E0E: "ฎ" THAI CHARACTER DO CHADA -->
+            <Key
+                latin:keyLabel="&#x0E0E;" />
+            <!-- U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO -->
+            <Key
+                latin:keyLabel="&#x0E11;" />
+            <!-- U+0E18: "ธ" THAI CHARACTER THO THONG -->
+            <Key
+                latin:keyLabel="&#x0E18;" />
+            <!-- U+0E4D: "กํ" THAI CHARACTER THANTHAKHAT -->
+            <Key
+                latin:keyLabel="&#x0E4D;" />
+            <!-- U+0E4A: "ก๊" THAI CHARACTER MAI TRI -->
+            <Key
+                latin:keyLabel="&#x0E4A;" />
+            <!-- U+0E13: "ณ" THAI CHARACTER NO NEN -->
+            <Key
+                latin:keyLabel="&#x0E13;" />
+            <!-- U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI -->
+            <Key
+                latin:keyLabel="&#x0E2F;" />
+            <!-- U+0E0D: "ญ" THAI CHARACTER YO YING -->
+            <Key
+                latin:keyLabel="&#x0E0D;" />
+            <!-- U+0E10: "ฐ" THAI CHARACTER THO THAN -->
+            <Key
+                latin:keyLabel="&#x0E10;" />
+            <Key
+                latin:keyLabel="," />
+            <!-- U+0E05: "ฅ" THAI CHARACTER KHO KHON -->
+            <Key
+                latin:keyLabel="&#x0E05;" />
+        </case>
+        <default>
+            <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK -->
+            <Key
+                latin:keyLabel="&#x0E46;" />
+            <!-- U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI -->
+            <Key
+                latin:keyLabel="&#x0E44;" />
+            <!-- U+0E33: "ำ" THAI CHARACTER SARA AM -->
+            <Key
+                latin:keyLabel="&#x0E33;" />
+            <!-- U+0E1E: "พ" THAI CHARACTER PHO PHAN -->
+            <Key
+                latin:keyLabel="&#x0E1E;" />
+            <!-- U+0E30: "ะ" THAI CHARACTER SARA A -->
+            <Key
+                latin:keyLabel="&#x0E30;" />
+            <!-- U+0E31: "กั" THAI CHARACTER MAI HAN-AKAT -->
+            <Key
+                latin:keyLabel="&#x0E31;" />
+            <!-- U+0E35: "กี" HAI CHARACTER SARA II -->
+            <Key
+                latin:keyLabel="&#x0E35;" />
+            <!-- U+0E23: "ร" THAI CHARACTER RO RUA -->
+            <Key
+                latin:keyLabel="&#x0E23;" />
+            <!-- U+0E19: "น" THAI CHARACTER NO NU -->
+            <Key
+                latin:keyLabel="&#x0E19;" />
+            <!-- U+0E22: "ย" THAI CHARACTER YO YAK -->
+            <Key
+                latin:keyLabel="&#x0E22;" />
+            <!-- U+0E1A: "บ" THAI CHARACTER BO BAIMAI -->
+            <Key
+                latin:keyLabel="&#x0E1A;" />
+            <!-- U+0E25: "ล" THAI CHARACTER LO LING -->
+            <Key
+                latin:keyLabel="&#x0E25;" />
+            <!-- U+0E03: "ฃ" THAI CHARACTER KHO KHUAT -->
+            <Key
+                latin:keyLabel="&#x0E03;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai3.xml b/java/res/xml-sw600dp/rowkeys_thai3.xml
new file mode 100644
index 0000000..abd6763
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_thai3.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0E24: "ฤ" THAI CHARACTER RU -->
+            <Key
+                latin:keyLabel="&#x0E24;" />
+            <!-- U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG -->
+            <Key
+                latin:keyLabel="&#x0E06;" />
+            <!-- U+0E0F: "ฏ" THAI CHARACTER TO PATAK -->
+            <Key
+                latin:keyLabel="&#x0E0F;" />
+            <!-- U+0E42: "โ" THAI CHARACTER SARA O -->
+            <Key
+                latin:keyLabel="&#x0E42;" />
+            <!-- U+0E0C: "ฌ" THAI CHARACTER CHO CHOE -->
+            <Key
+                latin:keyLabel="&#x0E0C;" />
+            <!-- U+0E47: " ็" THAI CHARACTER MAITAIKHU -->
+            <Key
+                latin:keyLabel="&#x0E47;" />
+            <!-- U+0E4B: " ๋" THAI CHARACTER MAI CHATTAWA -->
+            <Key
+                latin:keyLabel="&#x0E4B;" />
+            <!-- U+0E29: "ษ" THAI CHARACTER SO RUSI -->
+            <Key
+                latin:keyLabel="&#x0E29;" />
+            <!--  U+0E28: "ศ" THAI CHARACTER SO SALA -->
+            <Key
+                latin:keyLabel="&#x0E28;" />
+            <!-- U+0E0B: "ซ" THAI CHARACTER SO SO -->
+            <Key
+                latin:keyLabel="&#x0E0B;" />
+            <Key
+                latin:keyLabel="." />
+        </case>
+        <default>
+            <!-- U+0E1F: "ฟ" THAI CHARACTER FO FAN -->
+            <Key
+                latin:keyLabel="&#x0E1F;" />
+            <!-- U+0E2B: "ห" THAI CHARACTER HO HIP -->
+            <Key
+                latin:keyLabel="&#x0E2B;" />
+            <!-- U+0E01: "ก" THAI CHARACTER KO KAI -->
+            <Key
+                latin:keyLabel="&#x0E01;" />
+            <!-- U+0E14: "ด" THAI CHARACTER DO DEK -->
+            <Key
+                latin:keyLabel="&#x0E14;" />
+            <!-- U+0E40: "เ" THAI CHARACTER SARA E -->
+            <Key
+                latin:keyLabel="&#x0E40;" />
+            <!-- U+0E49: " ้" THAI CHARACTER MAI THO -->
+            <Key
+                latin:keyLabel="&#x0E49;" />
+            <!-- U+0E48: " ฺ" THAI CHARACTER MAI EK -->
+            <Key
+                latin:keyLabel="&#x0E48;" />
+            <!-- U+0E32: "า" THAI CHARACTER SARA AA -->
+            <Key
+                latin:keyLabel="&#x0E32;" />
+            <!-- U+0E2A: "ส" THAI CHARACTER SO SUA -->
+            <Key
+                latin:keyLabel="&#x0E2A;" />
+            <!-- U+0E27: "ว" THAI CHARACTER WO WAEN -->
+            <Key
+                latin:keyLabel="&#x0E27;" />
+            <!-- U+0E07: "ง" THAI CHARACTER NGO NGU -->
+            <Key
+                latin:keyLabel="&#x0E07;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai4.xml b/java/res/xml-sw600dp/rowkeys_thai4.xml
new file mode 100644
index 0000000..cec34a6
--- /dev/null
+++ b/java/res/xml-sw600dp/rowkeys_thai4.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <Key
+                latin:keyLabel="(" />
+            <Key
+                latin:keyLabel=")" />
+            <!-- U+0E09: "ฉ" THAI CHARACTER CHO CHING -->
+            <Key
+                latin:keyLabel="&#x0E09;" />
+            <!-- U+0E2E: "ฮ" THAI CHARACTER HO NOKHUK -->
+            <Key
+                latin:keyLabel="&#x0E2E;" />
+            <!-- U+0E3A: " ฺ" THAI CHARACTER PHINTHU -->
+            <Key
+                latin:keyLabel="&#x0E3A;" />
+            <!-- U+0E4C: " ์" THAI CHARACTER THANTHAKHAT -->
+            <Key
+                latin:keyLabel="&#x0E4C;" />
+            <Key
+                latin:keyLabel="\?" />
+            <!-- U+0E12: "ฒ" THAI CHARACTER THO PHUTHAO -->
+            <Key
+                latin:keyLabel="&#x0E12;" />
+            <!-- U+0E2C: "ฬ" THAI CHARACTER LO CHULA -->
+            <Key
+                latin:keyLabel="&#x0E2C;" />
+            <!-- U+0E26: "ฦ" THAI CHARACTER LU -->
+            <Key
+                latin:keyLabel="&#x0E26;" />
+        </case>
+        <default>
+            <!-- U+0E1C: "ผ" THAI CHARACTER PHO PHUNG -->
+            <Key
+                latin:keyLabel="&#x0E1C;" />
+            <!-- U+0E1B: "ป" THAI CHARACTER PO PLA -->
+            <Key
+                latin:keyLabel="&#x0E1B;" />
+            <!-- U+0E41: "แ" THAI CHARACTER SARA AE -->
+            <Key
+                latin:keyLabel="&#x0E41;" />
+            <!-- U+0E2D: "อ" THAI CHARACTER O ANG -->
+            <Key
+                latin:keyLabel="&#x0E2D;" />
+            <!-- U+0E34: " ิ" THAI CHARACTER SARA I -->
+            <Key
+                latin:keyLabel="&#x0E34;" />
+            <!-- U+0E37: " ื" THAI CHARACTER SARA UEE -->
+            <Key
+                latin:keyLabel="&#x0E37;" />
+            <!-- U+0E17: "ท" THAI CHARACTER THO THAHAN -->
+            <Key
+                latin:keyLabel="&#x0E17;" />
+            <!-- U+0E21: "ม" THAI CHARACTER MO MA -->
+            <Key
+                latin:keyLabel="&#x0E21;" />
+            <!-- U+0E43: "ใ" THAI CHARACTER SARA AI MAIMUAN -->
+            <Key
+                latin:keyLabel="&#x0E43;" />
+            <!-- U+0E1D: "ฝ" THAI CHARACTER FO FA -->
+            <Key
+                latin:keyLabel="&#x0E1D;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml b/java/res/xml-sw600dp/rows_10_10_7_symbols.xml
new file mode 100644
index 0000000..bdb1aa0
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_10_10_7_symbols.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+        <Row
+        latin:keyWidth="8.5%p"
+    >
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols3" />
+        <Key
+            latin:keyLabel="/"
+            latin:keyXPos="-8.5%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_10_10_7_symbols_shift.xml b/java/res/xml-sw600dp/rows_10_10_7_symbols_shift.xml
new file mode 100644
index 0000000..1014934
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_10_10_7_symbols_shift.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols_shift4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_arabic.xml b/java/res/xml-sw600dp/rows_arabic.xml
new file mode 100644
index 0000000..5522326
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_arabic.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic3" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_azerty.xml b/java/res/xml-sw600dp/rows_azerty.xml
new file mode 100644
index 0000000..3ec22d3
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_azerty.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-8.5%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_bulgarian.xml b/java/res/xml-sw600dp/rows_bulgarian.xml
new file mode 100644
index 0000000..a3b77cc
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_bulgarian.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian2"
+            latin:keyXPos="4.500%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_east_slavic.xml b/java/res/xml-sw600dp/rows_east_slavic.xml
new file mode 100644
index 0000000..26fd7df
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_east_slavic.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+        <Key
+            latin:keyLabel="&#x044A;" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.363%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_farsi.xml b/java/res/xml-sw600dp/rows_farsi.xml
new file mode 100644
index 0000000..7580042
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_farsi.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_georgian.xml b/java/res/xml-sw600dp/rows_georgian.xml
new file mode 100644
index 0000000..d4c39af4
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_georgian.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_greek.xml b/java/res/xml-sw600dp/rows_greek.xml
new file mode 100644
index 0000000..8314222
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_greek.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/key_greek_semicolon" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_hebrew.xml b/java/res/xml-sw600dp/rows_hebrew.xml
new file mode 100644
index 0000000..a60da3a
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_hebrew.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/keys_apostrophe_dash" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew3"
+            latin:keyXPos="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-8.5%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_hindi.xml b/java/res/xml-sw600dp/rows_hindi.xml
new file mode 100644
index 0000000..6c98e08
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_hindi.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi2"
+            latin:keyXPos="4.500%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_nordic.xml b/java/res/xml-sw600dp/rows_nordic.xml
new file mode 100644
index 0000000..3a8aa1d
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_nordic.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_number_normal.xml b/java/res/xml-sw600dp/rows_number_normal.xml
new file mode 100644
index 0000000..be5776b
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_number_normal.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="12.75%p" />
+        <Key
+            latin:keyLabel="-"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel="+"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel="."
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel="1"
+            latin:keyStyle="numKeyStyle"
+            latin:keyXPos="42.25%p" />
+        <Key
+            latin:keyLabel="2"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="3"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="12.75%p" />
+        <Key
+            latin:keyStyle="numStarKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel="/"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <switch>
+            <case
+                latin:mode="time|datetime"
+            >
+                <Key
+                    latin:keyLabel=","
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:moreKeys="@string/more_keys_for_am_pm"
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="9.25%p" />
+            </case>
+            <default>
+                <Key
+                    latin:keyLabel=","
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="9.25%p" />
+            </default>
+        </switch>
+        <Key
+            latin:keyLabel="4"
+            latin:keyStyle="numKeyStyle"
+            latin:keyXPos="42.25%p" />
+        <Key
+            latin:keyLabel="5"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="6"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="12.75%p" />
+        <Key
+            latin:keyLabel="("
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel=")"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <switch>
+            <case
+                latin:mode="time|datetime"
+            >
+                <Key
+                    latin:keyLabel=":"
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="9.25%p" />
+            </case>
+            <default>
+                <Key
+                    latin:keyLabel="="
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="9.25%p" />
+            </default>
+        </switch>
+        <Key
+            latin:keyLabel="7"
+            latin:keyStyle="numKeyStyle"
+            latin:keyXPos="42.25%p" />
+        <Key
+            latin:keyLabel="8"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="9"
+            latin:keyStyle="numKeyStyle" />
+        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
+        <Spacer
+            latin:keyWidth="0%p" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyStyle="numTabKeyStyle"
+            latin:keyWidth="11.00%p" />
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyWidth="27.75%p"
+            latin:keyXPos="12.75%p" />
+        <Key
+            latin:keyStyle="numStarKeyStyle"
+            latin:keyXPos="42.25%p" />
+        <Key
+            latin:keyLabel="0"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="#"
+            latin:keyStyle="numKeyStyle" />
+        <Spacer
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="0%p" />
+        <include
+            latin:keyboardLayout="@xml/key_shortcut" />
+    </Row>
+</merge>
diff --git a/java/res/xml-sw600dp/rows_number_password.xml b/java/res/xml-sw600dp/rows_number_password.xml
new file mode 100644
index 0000000..59279fb
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_number_password.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="27.50%p" />
+        <Key
+            latin:keyStyle="num1KeyStyle" />
+        <Key
+            latin:keyStyle="num2KeyStyle" />
+        <Key
+            latin:keyStyle="num3KeyStyle" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="27.50%p" />
+        <Key
+            latin:keyStyle="num4KeyStyle" />
+        <Key
+            latin:keyStyle="num5KeyStyle" />
+        <Key
+            latin:keyStyle="num6KeyStyle" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="27.50%p" />
+        <Key
+            latin:keyStyle="num7KeyStyle" />
+        <Key
+            latin:keyStyle="num8KeyStyle" />
+        <Key
+            latin:keyStyle="num9KeyStyle" />
+        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
+        <Spacer
+            latin:keyWidth="0%p" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyWidth="11.00%p" />
+        <Key
+            latin:keyStyle="num0KeyStyle"
+            latin:keyXPos="42.50%p"/>
+        <Spacer
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="0%p" />
+        <include
+            latin:keyboardLayout="@xml/key_shortcut" />
+    </Row>
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_phone_shift.xml b/java/res/xml-sw600dp/rows_phone.xml
similarity index 72%
rename from java/res/xml-sw600dp/kbd_phone_shift.xml
rename to java/res/xml-sw600dp/rows_phone.xml
index 4c4f8ad..e892693 100644
--- a/java/res/xml-sw600dp/kbd_phone_shift.xml
+++ b/java/res/xml-sw600dp/rows_phone.xml
@@ -18,18 +18,17 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="16.75%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
+        latin:keyboardLayout="@xml/key_styles_common" />
     <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
+        latin:keyboardLayout="@xml/key_styles_number" />
     <Row>
         <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
         <Spacer
-            latin:keyWidth="11.00%p" />
+            latin:keyWidth="12.75%p" />
         <Key
             latin:keyLabel="-"
             latin:keyStyle="numKeyStyle"
@@ -39,13 +38,11 @@
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="9.25%p" />
         <Key
-            latin:code="44"
-            latin:keyLabel="@string/label_pause_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
+            latin:keyStyle="numPauseKeyStyle"
             latin:keyWidth="9.25%p" />
         <Key
             latin:keyStyle="num1KeyStyle"
-            latin:keyXPos="38.867%p" />
+            latin:keyXPos="42.25%p" />
         <Key
             latin:keyStyle="num2KeyStyle" />
         <Key
@@ -53,12 +50,12 @@
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyXPos="-11.00%p"
-            latin:keyWidth="fillBoth" />
+            latin:keyWidth="fillRight" />
     </Row>
     <Row>
         <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
         <Spacer
-            latin:keyWidth="11.00%p" />
+            latin:keyWidth="12.75%p" />
         <Key
             latin:keyLabel=","
             latin:keyStyle="numKeyStyle"
@@ -68,30 +65,29 @@
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="9.25%p" />
         <Key
-            latin:code="59"
-            latin:keyLabel="@string/label_wait_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
+            latin:keyStyle="numWaitKeyStyle"
             latin:keyWidth="9.25%p" />
         <Key
             latin:keyStyle="num4KeyStyle"
-            latin:keyXPos="38.867%p" />
+            latin:keyXPos="42.25%p" />
         <Key
             latin:keyStyle="num5KeyStyle" />
         <Key
             latin:keyStyle="num6KeyStyle" />
         <Key
-            latin:keyStyle="returnKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyXPos="-11.00%p"
-            latin:keyWidth="fillBoth" />
+            latin:keyWidth="fillRight" />
     </Row>
     <Row>
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="11.00%p" />
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="12.75%p" />
         <Key
             latin:keyLabel="("
             latin:keyStyle="numKeyStyle"
-            latin:keyWidth="9.25%p" />
+            latin:keyWidth="9.25%p"
+            latin:keyXPos="12.75%p" />
         <Key
             latin:keyLabel=")"
             latin:keyStyle="numKeyStyle"
@@ -102,7 +98,7 @@
             latin:keyWidth="9.25%p" />
         <Key
             latin:keyStyle="num7KeyStyle"
-            latin:keyXPos="38.867%p" />
+            latin:keyXPos="42.25%p" />
         <Key
             latin:keyStyle="num8KeyStyle" />
         <Key
@@ -113,14 +109,15 @@
     </Row>
     <Row>
         <Key
-            latin:keyStyle="tabKeyStyle"
+            latin:keyStyle="numTabKeyStyle"
             latin:keyWidth="11.00%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
-            latin:keyWidth="27.75%p" />
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyWidth="27.75%p"
+            latin:keyXPos="12.75%p" />
         <Key
             latin:keyStyle="numStarKeyStyle"
-            latin:keyXPos="38.867%p" />
+            latin:keyXPos="42.25%p" />
         <Key
             latin:keyStyle="num0KeyStyle" />
         <Key
@@ -130,6 +127,6 @@
             latin:keyXPos="-11.00%p"
             latin:keyWidth="0%p" />
         <include
-            latin:keyboardLayout="@xml/kbd_qwerty_f2" />
+            latin:keyboardLayout="@xml/key_shortcut" />
     </Row>
-</Keyboard>
+</merge>
diff --git a/java/res/xml-sw600dp/rows_qwerty.xml b/java/res/xml-sw600dp/rows_qwerty.xml
new file mode 100644
index 0000000..8e8d5ac
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_qwerty.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_qwertz.xml b/java/res/xml-sw600dp/rows_qwertz.xml
new file mode 100644
index 0000000..d8f5bc6
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_qwertz.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_south_slavic.xml b/java/res/xml-sw600dp/rows_south_slavic.xml
new file mode 100644
index 0000000..8636cbb
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_south_slavic.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.6%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.363%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_spanish.xml b/java/res/xml-sw600dp/rows_spanish.xml
new file mode 100644
index 0000000..9451e42
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_spanish.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_spanish2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-8.5%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_symbols.xml b/java/res/xml-sw600dp/rows_symbols.xml
new file mode 100644
index 0000000..ea9b302
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_symbols.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+        <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols3" />
+        <Key
+            latin:keyLabel="/"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_symbols4.xml b/java/res/xml-sw600dp/rows_symbols4.xml
new file mode 100644
index 0000000..bfc9b2c
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_symbols4.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="8.9%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="13.0%p" />
+        <Key
+            latin:keyStyle="tabKeyStyle" />
+        <Key
+            latin:keyLabel="\@" />
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyXPos="30.750%p"
+            latin:keyWidth="39.750%p" />
+        <Key
+            latin:keyLabel="&quot;"
+            latin:moreKeys="@string/more_keys_for_tablet_double_quote" />
+        <Key
+            latin:keyLabel="_" />
+        <Spacer
+            latin:keyXPos="-10.00%p"
+            latin:keyWidth="0%p" />
+        <include
+            latin:keyboardLayout="@xml/key_shortcut" />
+    </Row>
+</merge>
diff --git a/java/res/xml-sw600dp/rows_symbols_shift.xml b/java/res/xml-sw600dp/rows_symbols_shift.xml
new file mode 100644
index 0000000..cc66f96
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_symbols_shift.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift2"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols_shift4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_symbols_shift4.xml b/java/res/xml-sw600dp/rows_symbols_shift4.xml
new file mode 100644
index 0000000..4381bce
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_symbols_shift4.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="8.9%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="13.0%p" />
+        <Key
+            latin:keyStyle="tabKeyStyle" />
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyXPos="30.750%p"
+            latin:keyWidth="39.750%p" />
+        <Spacer
+            latin:keyXPos="-10.00%p"
+            latin:keyWidth="0%p" />
+        <include
+            latin:keyboardLayout="@xml/key_shortcut" />
+    </Row>
+</merge>
diff --git a/java/res/xml-sw600dp/rows_thai.xml b/java/res/xml-sw600dp/rows_thai.xml
new file mode 100644
index 0000000..d4eaa10
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_thai.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.800%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai1"
+            latin:keyXPos="4.0%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+        </Row>
+    <Row
+        latin:keyWidth="7.692%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai2" />
+    </Row>
+    <Row
+        latin:keyWidth="7.692%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai3"
+            latin:keyXPos="4.5%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.692%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai4" />
+        <include
+            latin:keyboardLayout="@xml/key_smiley"
+            latin:keyXPos="-9.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp-land/kbd_mini_keyboard_template.xml b/java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
similarity index 95%
rename from java/res/xml-sw768dp-land/kbd_mini_keyboard_template.xml
rename to java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
index 85e864a..f593fa9 100644
--- a/java/res/xml-sw768dp-land/kbd_mini_keyboard_template.xml
+++ b/java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
@@ -21,6 +21,6 @@
 <Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="3.5%p"
     latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/miniKeyboardStyle"
+    style="?attr/moreKeysKeyboardStyle"
     >
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_number.xml
similarity index 85%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_number.xml
index 3195d5b..3ad25a3 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_number.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_phone.xml
similarity index 85%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_phone.xml
index 3195d5b..abe7e7c 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_phone.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
similarity index 80%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_phone_symbols.xml
index 3195d5b..641464d 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_thai.xml
similarity index 77%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_thai.xml
index e29d9ab..b2cdbc3 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_thai.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="2.65%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_thai_symbols.xml
similarity index 77%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_thai_symbols.xml
index e29d9ab..1531458 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_thai_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="2.65%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_thai_symbols_shift.xml
similarity index 76%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_thai_symbols_shift.xml
index e29d9ab..fa30f24 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_thai_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="2.65%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_key_styles.xml b/java/res/xml-sw768dp/kbd_key_styles.xml
deleted file mode 100644
index f16f5b6..0000000
--- a/java/res/xml-sw768dp/kbd_key_styles.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <key-style
-        latin:styleName="shiftKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyIcon="iconShiftKey"
-        latin:keyIconShifted="iconShiftedShiftKey"
-        latin:backgroundType="sticky" />
-    <key-style
-        latin:styleName="deleteKeyStyle"
-        latin:code="@integer/key_delete"
-        latin:keyIcon="iconDeleteKey"
-        latin:backgroundType="functional"
-        latin:isRepeatable="true" />
-    <key-style
-        latin:styleName="returnKeyStyle"
-        latin:code="@integer/key_return"
-        latin:keyIcon="iconReturnKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="spaceKeyStyle"
-        latin:code="@integer/key_space" />
-    <key-style
-        latin:styleName="nonSpecialBackgroundSpaceKeyStyle"
-        latin:code="@integer/key_space" />
-    <key-style
-        latin:styleName="smileyKeyStyle"
-        latin:keyLabel=":-)"
-        latin:keyOutputText=":-) "
-        latin:keyLabelOption="hasPopupHint"
-        latin:moreKeys="@string/more_keys_for_smiley"
-        latin:maxMoreKeysColumn="5" />
-    <key-style
-        latin:styleName="settingsKeyStyle"
-        latin:code="@integer/key_settings"
-        latin:keyIcon="iconSettingsKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="shortcutKeyStyle"
-        latin:code="@integer/key_shortcut"
-        latin:keyIcon="iconShortcutKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="tabKeyStyle"
-        latin:code="@integer/key_tab"
-        latin:keyLabel="@string/label_tab_key"
-        latin:keyLabelOption="fontNormal"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toSymbolKeyStyle"
-        latin:code="@integer/key_switch_alpha_symbol"
-        latin:keyLabel="@string/label_to_symbol_key"
-        latin:keyLabelOption="fontNormal"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toAlphaKeyStyle"
-        latin:code="@integer/key_switch_alpha_symbol"
-        latin:keyLabel="@string/label_to_alpha_key"
-        latin:keyLabelOption="fontNormal"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toMoreSymbolKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyLabel="@string/label_to_more_symbol_for_tablet_key"
-        latin:keyLabelOption="fontNormal"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="backFromMoreSymbolKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyLabel="@string/label_to_symbol_key"
-        latin:keyLabelOption="fontNormal"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="comKeyStyle"
-        latin:keyLabel="@string/keylabel_for_popular_domain"
-        latin:keyLabelOption="fontNormal|hasPopupHint"
-        latin:keyOutputText="@string/keylabel_for_popular_domain"
-        latin:moreKeys="@string/more_keys_for_popular_domain" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_mini_keyboard_template.xml b/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
similarity index 95%
rename from java/res/xml-sw768dp/kbd_mini_keyboard_template.xml
rename to java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
index 409c605..f89a0a6 100644
--- a/java/res/xml-sw768dp/kbd_mini_keyboard_template.xml
+++ b/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
@@ -21,6 +21,6 @@
 <Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="5.0%p"
     latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/miniKeyboardStyle"
+    style="?attr/moreKeysKeyboardStyle"
     >
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_number.xml b/java/res/xml-sw768dp/kbd_number.xml
index 369e91a..b20123c 100644
--- a/java/res/xml-sw768dp/kbd_number.xml
+++ b/java/res/xml-sw768dp/kbd_number.xml
@@ -23,206 +23,5 @@
     latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
-    <switch>
-        <case
-            latin:passwordInput="true"
-        >
-            <Row>
-                <Key
-                    latin:keyStyle="tabKeyStyle"
-                    latin:keyLabelOption="alignLeft"
-                    latin:keyWidth="11.172%p" />
-                <Key
-                    latin:keyStyle="num1KeyStyle"
-                    latin:keyXPos="32.076%p" />
-                <Key
-                    latin:keyStyle="num2KeyStyle" />
-                <Key
-                    latin:keyStyle="num3KeyStyle" />
-                <Key
-                    latin:keyStyle="deleteKeyStyle"
-                    latin:keyXPos="-11.172%p"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="32.076%p" />
-                <Key
-                    latin:keyStyle="num4KeyStyle" />
-                <Key
-                    latin:keyStyle="num5KeyStyle" />
-                <Key
-                    latin:keyStyle="num6KeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyXPos="-11.172%p"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="32.076%p" />
-                <Key
-                    latin:keyStyle="num7KeyStyle" />
-                <Key
-                    latin:keyStyle="num8KeyStyle" />
-                <Key
-                    latin:keyStyle="num9KeyStyle" />
-                <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                <Spacer
-                    latin:keyWidth="0%p" />
-            </Row>
-            <Row>
-                <Spacer
-                    latin:keyXPos="32.076%p" />
-                <Key
-                    latin:keyStyle="num0KeyStyle" />
-                <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                <Spacer
-                    latin:keyWidth="0%p" />
-            </Row>
-        </case>
-        <!-- latin:passwordInput="false" -->
-        <default>
-            <Row>
-                <Key
-                    latin:keyStyle="tabKeyStyle"
-                    latin:keyLabelOption="alignLeft"
-                    latin:keyWidth="11.172%p" />
-                <Key
-                    latin:keyLabel="-"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="13.829%p"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="+"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="1"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="43.125%p" />
-                <Key
-                    latin:keyLabel="2"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="3"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyStyle="deleteKeyStyle"
-                    latin:keyXPos="-11.172%p"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="13.829%p" />
-                <Key
-                    latin:keyStyle="numStarKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="/"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel=","
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="4"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="43.125%p" />
-                <Key
-                    latin:keyLabel="5"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="6"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyXPos="-11.172%p"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="13.829%p" />
-                <Key
-                    latin:keyLabel="("
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel=")"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="="
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p" />
-                <Key
-                    latin:keyLabel="7"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyXPos="43.125%p" />
-                <Key
-                    latin:keyLabel="8"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="9"
-                    latin:keyStyle="numKeyStyle" />
-                <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                <Spacer
-                    latin:keyWidth="0%p" />
-            </Row>
-            <Row>
-                <switch>
-                    <case latin:hasSettingsKey="true">
-                        <Key
-                            latin:keyStyle="settingsKeyStyle"
-                            latin:keyWidth="8.047%p" />
-                    </case>
-                    <default>
-                        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                        <Spacer
-                            latin:keyWidth="8.047%p" />
-                    </default>
-                </switch>
-                <Key
-                    latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
-                    latin:keyXPos="13.829%p"
-                    latin:keyWidth="24.140%p" />
-                <Key
-                    latin:keyStyle="numStarKeyStyle"
-                    latin:keyXPos="43.125%p" />
-                <Key
-                    latin:keyLabel="0"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="#"
-                    latin:keyStyle="numKeyStyle" />
-                <switch>
-                    <case
-                        latin:shortcutKeyEnabled="true"
-                    >
-                        <Key
-                            latin:keyStyle="shortcutKeyStyle"
-                            latin:keyXPos="-8.047%p"
-                            latin:keyWidth="fillRight" />
-                    </case>
-                    <default>
-                        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                        <Spacer
-                            latin:keyWidth="0%p" />
-                    </default>
-                </switch>
-            </Row>
-        </default>
-    </switch>
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_phone.xml b/java/res/xml-sw768dp/kbd_phone.xml
index e55b184..fa9bf1b 100644
--- a/java/res/xml-sw768dp/kbd_phone.xml
+++ b/java/res/xml-sw768dp/kbd_phone.xml
@@ -23,122 +23,5 @@
     latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
-    <Row>
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel="-"
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="20.400%p"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyLabel="+"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="num1KeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num2KeyStyle" />
-        <Key
-            latin:keyStyle="num3KeyStyle" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel=","
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="20.400%p"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyLabel="."
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="num4KeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num5KeyStyle" />
-        <Key
-            latin:keyStyle="num6KeyStyle" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="20.400%p" />
-        <Key
-            latin:keyLabel="("
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyLabel=")"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="num7KeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num8KeyStyle" />
-        <Key
-            latin:keyStyle="num9KeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="0%p" />
-        </Row>
-    <Row>
-        <switch>
-            <case latin:hasSettingsKey="true">
-                <Key
-                    latin:keyStyle="settingsKeyStyle"
-                    latin:keyWidth="8.047%p" />
-            </case>
-            <default>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="8.047%p" />
-            </default>
-        </switch>
-        <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
-            latin:keyXPos="20.400%p"
-            latin:keyWidth="16.084%p" />
-        <Key
-            latin:keyStyle="numStarKeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num0KeyStyle" />
-        <Key
-            latin:keyLabel="#"
-            latin:keyStyle="numKeyStyle" />
-        <switch>
-            <case
-                latin:shortcutKeyEnabled="true"
-            >
-                <Key
-                    latin:keyStyle="shortcutKeyStyle"
-                    latin:keyXPos="-8.047%p"
-                    latin:keyWidth="fillRight" />
-            </case>
-            <default>
-                <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-                <Spacer
-                    latin:keyWidth="0%p" />
-            </default>
-        </switch>
-    </Row>
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-da/kbd_qwerty.xml b/java/res/xml-sw768dp/kbd_phone_symbols.xml
similarity index 84%
rename from java/res/xml-da/kbd_qwerty.xml
rename to java/res/xml-sw768dp/kbd_phone_symbols.xml
index 37a50fd..e1a359e 100644
--- a/java/res/xml-da/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp/kbd_phone_symbols.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="da"
+    latin:keyWidth="13.250%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row1.xml b/java/res/xml-sw768dp/kbd_qwerty_row1.xml
deleted file mode 100644
index 14b8bdd..0000000
--- a/java/res/xml-sw768dp/kbd_qwerty_row1.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-9.219%p"
-            latin:keyWidth="fillBoth"/>
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row2.xml b/java/res/xml-sw768dp/kbd_qwerty_row2.xml
deleted file mode 100644
index 2c312a3..0000000
--- a/java/res/xml-sw768dp/kbd_qwerty_row2.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="11.172%p"/>
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-15.704%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row3.xml b/java/res/xml-sw768dp/kbd_qwerty_row3.xml
deleted file mode 100644
index f2f137e..0000000
--- a/java/res/xml-sw768dp/kbd_qwerty_row3.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p"/>
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_row3_comma_period.xml b/java/res/xml-sw768dp/kbd_row3_comma_period.xml
deleted file mode 100644
index b844430..0000000
--- a/java/res/xml-sw768dp/kbd_row3_comma_period.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:mode="email"
-        >
-            <Key
-                latin:keyLabel="," />
-            <Key
-                latin:keyLabel="." />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel=","
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel="!"
-                latin:moreKeys="!" />
-            <Key
-                latin:keyLabel="."
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel="\?"
-                latin:moreKeys="\?" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml b/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml
deleted file mode 100644
index 9536e81..0000000
--- a/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:mode="email"
-        >
-            <Key
-                latin:keyLabel="-" />
-        </case>
-        <case
-            latin:mode="url"
-        >
-            <Key
-                latin:keyLabel="/"
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel=":"
-                latin:moreKeys=":" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="@string/keylabel_for_apostrophe"
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel="@string/keyhintlabel_for_apostrophe"
-                latin:moreKeys="@string/more_keys_for_apostrophe" />
-        </default>
-    </switch>
-    <switch>
-        <case
-            latin:mode="email"
-        >
-            <Key
-                latin:keyLabel="_" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="@string/keylabel_for_dash"
-                latin:keyLabelOption="hasUppercaseLetter"
-                latin:keyHintLabel="@string/keyhintlabel_for_dash"
-                latin:moreKeys="@string/more_keys_for_dash" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_arabic.xml b/java/res/xml-sw768dp/kbd_rows_arabic.xml
deleted file mode 100644
index 7ec36fd..0000000
--- a/java/res/xml-sw768dp/kbd_rows_arabic.xml
+++ /dev/null
@@ -1,193 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.500%p" />
-        <!-- \u0636: ARABIC LETTER DAD -->
-        <Key
-            latin:keyLabel="ض" />
-        <!-- \u0635: ARABIC LETTER SAD -->
-        <Key
-            latin:keyLabel="ص" />
-        <!-- \u062b: ARABIC LETTER THEH -->
-        <Key
-            latin:keyLabel="ث" />
-        <!-- \u0642: ARABIC LETTER QAF
-             \u06a8: ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
-        <Key
-            latin:keyLabel="ق"
-            latin:moreKeys="ڨ" />
-        <!-- \u0641: ARABIC LETTER FEH
-             \u06a4: ARABIC LETTER VEH
-             \u06a2: ARABIC LETTER FEH WITH DOT MOVED BELOW
-             \u06a5: ARABIC LETTER FEH WITH THREE DOTS BELOW -->
-        <Key
-            latin:keyLabel="ف"
-            latin:moreKeys="\u06a4,\u06a2,\u06a5" />
-        <!-- \u063a: ARABIC LETTER GHAIN -->
-        <Key
-            latin:keyLabel="غ" />
-        <!-- \u0639: ARABIC LETTER AIN -->
-        <Key
-            latin:keyLabel="ع" />
-        <!-- \u0647: ARABIC LETTER HEH
-             \ufeeb: ARABIC LETTER HEH INITIAL FORM
-             \u0647\u0640: ARABIC LETTER HEH + Zero width joiner -->
-        <Key
-            latin:keyLabel="ه"
-            latin:moreKeys="\ufeeb|\u0647\u200D" />
-        <!-- \u062e: ARABIC LETTER KHAH -->
-        <Key
-            latin:keyLabel="خ" />
-        <!-- \u062d: ARABIC LETTER HAH -->
-        <Key
-            latin:keyLabel="ح" />
-        <!-- \u062c: ARABIC LETTER JEEM
-             \u0686: ARABIC LETTER TCHEH -->
-        <Key
-            latin:keyLabel="ج"
-            latin:moreKeys="چ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.500%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="9.375%p" />
-        <!-- \u0634: ARABIC LETTER SHEEN
-             \u069c: ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-        <Key
-            latin:keyLabel="ش"
-            latin:moreKeys="ڜ" />
-        <!-- \u0633: ARABIC LETTER SEEN -->
-        <Key
-            latin:keyLabel="س" />
-        <!-- \u064a: ARABIC LETTER YEH
-             \u0626: ARABIC LETTER YEH WITH HAMZA ABOVE
-             \u0649: ARABIC LETTER ALEF MAKSURA -->
-        <Key
-            latin:keyLabel="ي"
-            latin:moreKeys="\u0626,\u0649" />
-        <!-- \u0628: ARABIC LETTER BEH
-             \u067e: ARABIC LETTER PEH -->
-        <Key
-            latin:keyLabel="ب"
-            latin:moreKeys="پ" />
-        <!-- \u0644: ARABIC LETTER LAM
-             \ufefb: ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
-             \u0627: ARABIC LETTER ALEF
-             \ufef7: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
-             \u0623: ARABIC LETTER ALEF WITH HAMZA ABOVE
-             \ufef9: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
-             \u0625: ARABIC LETTER ALEF WITH HAMZA BELOW
-             \ufef5: ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
-             \u0622: ARABIC LETTER ALEF WITH MADDA ABOVE -->
-        <Key
-            latin:keyLabel="ل"
-            latin:moreKeys="\ufefb|\u0644\u0627,\ufef7|\u0644\u0623,\ufef9|\u0644\u0625,\ufef5|\u0644\u0622" />
-        <!-- \u0627: ARABIC LETTER ALEF
-             \u0621: ARABIC LETTER HAMZA
-             \u0671: ARABIC LETTER ALEF WASLA
-             \u0623: ARABIC LETTER ALEF WITH HAMZA ABOVE
-             \u0625: ARABIC LETTER ALEF WITH HAMZA BELOW
-             \u0622: ARABIC LETTER ALEF WITH MADDA ABOVE -->
-        <Key
-            latin:keyLabel="ا"
-            latin:moreKeys="\u0621,\u0671,\u0623,\u0625,\u0622" />
-        <!-- \u062a: ARABIC LETTER TEH -->
-        <Key
-            latin:keyLabel="ت" />
-        <!-- \u0646: ARABIC LETTER NOON -->
-        <Key
-            latin:keyLabel="ن" />
-        <!-- \u0645: ARABIC LETTER MEEM -->
-        <Key
-            latin:keyLabel="م" />
-        <!-- \u0643: ARABIC LETTER KAF
-             \u06af: ARABIC LETTER GAF -->
-        <Key
-            latin:keyLabel="ك"
-            latin:moreKeys="گ" />
-        <!-- \u0637: ARABIC LETTER TAH -->
-        <Key
-            latin:keyLabel="ط" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-9.375%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <!-- \u0626: ARABIC LETTER YEH WITH HAMZA ABOVE -->
-        <Key
-            latin:keyLabel="ئ"
-            latin:keyXPos="12.750%p" />
-        <!-- \u0621: ARABIC LETTER HAMZA -->
-        <Key
-            latin:keyLabel="ء" />
-        <!-- \u0624: ARABIC LETTER WAW WITH HAMZA ABOVE -->
-        <Key
-            latin:keyLabel="ؤ" />
-        <!-- \u0631: ARABIC LETTER REH -->
-        <Key
-            latin:keyLabel="ر" />
-        <!-- \u0630: ARABIC LETTER THAL -->
-        <Key
-            latin:keyLabel="ذ" />
-        <!-- \u0649: ARABIC LETTER ALEF MAKSURA -->
-        <Key
-            latin:keyLabel="ى" />
-        <!-- \u0629: ARABIC LETTER TEH MARBUTA -->
-        <Key
-            latin:keyLabel="ة" />
-        <!-- \u0648: ARABIC LETTER WAW -->
-        <Key
-            latin:keyLabel="و" />
-        <!-- \u0632: ARABIC LETTER ZAIN
-             \u0698: ARABIC LETTER JEH -->
-        <Key
-            latin:keyLabel="ز"
-            latin:moreKeys="ژ" />
-        <!-- \u0638: ARABIC LETTER ZAH -->
-        <Key
-            latin:keyLabel="ظ" />
-        <!-- \u062f: ARABIC LETTER DAL -->
-        <Key
-            latin:keyLabel="د" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_azerty.xml b/java/res/xml-sw768dp/kbd_rows_azerty.xml
deleted file mode 100644
index 4659d99..0000000
--- a/java/res/xml-sw768dp/kbd_rows_azerty.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-9.219%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="m" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-15.704%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="\'"
-            latin:keyLabelOption="hasUppercaseLetter"
-            latin:keyHintLabel=":"
-            latin:moreKeys=":" />
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="," />
-                <Key
-                    latin:keyLabel="." />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="!"
-                    latin:moreKeys="!" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="\?"
-                    latin:moreKeys="\?" />
-            </default>
-        </switch>
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_hebrew.xml b/java/res/xml-sw768dp/kbd_rows_hebrew.xml
deleted file mode 100644
index 27b39d1..0000000
--- a/java/res/xml-sw768dp/kbd_rows_hebrew.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row4_apostrophe_dash" />
-        <Key
-            latin:keyLabel="ק" />
-        <Key
-            latin:keyLabel="ר" />
-        <Key
-            latin:keyLabel="א" />
-        <Key
-            latin:keyLabel="ט" />
-        <Key
-            latin:keyLabel="ו" />
-        <Key
-            latin:keyLabel="ן" />
-        <Key
-            latin:keyLabel="ם" />
-        <Key
-            latin:keyLabel="פ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-12.000%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel="ש" />
-        <Key
-            latin:keyLabel="ד" />
-        <Key
-            latin:keyLabel="ג"
-            latin:moreKeys="ג׳" />
-        <Key
-            latin:keyLabel="כ" />
-        <Key
-            latin:keyLabel="ע" />
-        <Key
-            latin:keyLabel="י"
-            latin:moreKeys="ײַ" />
-        <Key
-            latin:keyLabel="ח"
-            latin:moreKeys="ח׳" />
-        <Key
-            latin:keyLabel="ל" />
-        <Key
-            latin:keyLabel="ך" />
-        <Key
-            latin:keyLabel="ף" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyLabel="ז"
-            latin:moreKeys="ז׳"
-            latin:keyXPos="13.829%p" />
-        <Key
-            latin:keyLabel="ס" />
-        <Key
-            latin:keyLabel="ב" />
-        <Key
-            latin:keyLabel="ה" />
-        <Key
-            latin:keyLabel="נ" />
-        <Key
-            latin:keyLabel="מ" />
-        <Key
-            latin:keyLabel="צ"
-            latin:moreKeys="צ׳" />
-        <Key
-            latin:keyLabel="ת"
-            latin:moreKeys="ת׳" />
-        <Key
-            latin:keyLabel="ץ"
-            latin:moreKeys="ץ׳" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-10.400%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_qwertz.xml b/java/res/xml-sw768dp/kbd_rows_qwertz.xml
deleted file mode 100644
index 82e0dd0..0000000
--- a/java/res/xml-sw768dp/kbd_rows_qwertz.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-9.219%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <switch>
-            <case
-                latin:mode="email"
-            >
-                <Key
-                    latin:keyLabel="," />
-                <Key
-                    latin:keyLabel="." />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="!"
-                    latin:moreKeys="!" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
-                    latin:keyHintLabel="\?"
-                    latin:moreKeys="\?" />
-            </default>
-        </switch>
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-   <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_russian.xml b/java/res/xml-sw768dp/kbd_rows_russian.xml
deleted file mode 100644
index e5f5569..0000000
--- a/java/res/xml-sw768dp/kbd_rows_russian.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="7.125%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft" />
-        <Key
-            latin:keyLabel="й" />
-        <Key
-            latin:keyLabel="ц" />
-        <Key
-            latin:keyLabel="у" />
-        <Key
-            latin:keyLabel="к" />
-        <Key
-            latin:keyLabel="е"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
-        <Key
-            latin:keyLabel="н" />
-        <Key
-            latin:keyLabel="г" />
-        <Key
-            latin:keyLabel="ш" />
-        <Key
-            latin:keyLabel="щ" />
-        <Key
-            latin:keyLabel="з" />
-        <Key
-            latin:keyLabel="х" />
-        <Key
-            latin:keyLabel="ъ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="9.375%p" />
-        <Key
-            latin:keyLabel="ф" />
-        <Key
-            latin:keyLabel="ы" />
-        <Key
-            latin:keyLabel="в" />
-        <Key
-            latin:keyLabel="а" />
-        <Key
-            latin:keyLabel="п" />
-        <Key
-            latin:keyLabel="р" />
-        <Key
-            latin:keyLabel="о" />
-        <Key
-            latin:keyLabel="л" />
-        <Key
-            latin:keyLabel="д" />
-        <Key
-            latin:keyLabel="ж" />
-        <Key
-            latin:keyLabel="э" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-9.375%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.125%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="12.750%p" />
-        <Key
-            latin:keyLabel="я" />
-        <Key
-            latin:keyLabel="ч" />
-        <Key
-            latin:keyLabel="с" />
-        <Key
-            latin:keyLabel="м" />
-        <Key
-            latin:keyLabel="и" />
-        <Key
-            latin:keyLabel="т" />
-        <Key
-            latin:keyLabel="ь" />
-        <Key
-            latin:keyLabel="б" />
-        <Key
-            latin:keyLabel="ю" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_scandinavian.xml b/java/res/xml-sw768dp/kbd_rows_scandinavian.xml
deleted file mode 100644
index b9d1680..0000000
--- a/java/res/xml-sw768dp/kbd_rows_scandinavian.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.500%p" />
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyLabel="å" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.500%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="9.375%p" />
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_scandinavia_row2_10"
-            latin:moreKeys="@string/more_keys_for_scandinavia_row2_10" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_scandinavia_row2_11"
-            latin:moreKeys="@string/more_keys_for_scandinavia_row2_11" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-9.375%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="12.750%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyXPos="-12.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_serbian.xml b/java/res/xml-sw768dp/kbd_rows_serbian.xml
deleted file mode 100644
index c07176e..0000000
--- a/java/res/xml-sw768dp/kbd_rows_serbian.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="7.125%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft" />
-        <Key
-            latin:keyLabel="љ" />
-        <Key
-            latin:keyLabel="њ" />
-        <Key
-            latin:keyLabel="е" />
-        <Key
-            latin:keyLabel="р" />
-        <Key
-            latin:keyLabel="т" />
-        <Key
-            latin:keyLabel="з" />
-        <Key
-            latin:keyLabel="у" />
-        <Key
-            latin:keyLabel="и" />
-        <Key
-            latin:keyLabel="о" />
-        <Key
-            latin:keyLabel="п" />
-        <Key
-            latin:keyLabel="ш" />
-        <Key
-            latin:keyLabel="ђ" />
-        <Key
-            latin:keyLabel="ж"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.250%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel="а" />
-        <Key
-            latin:keyLabel="с" />
-        <Key
-            latin:keyLabel="д" />
-        <Key
-            latin:keyLabel="ф" />
-        <Key
-            latin:keyLabel="г" />
-        <Key
-            latin:keyLabel="х" />
-        <Key
-            latin:keyLabel="ј" />
-        <Key
-            latin:keyLabel="к" />
-        <Key
-            latin:keyLabel="л" />
-        <Key
-            latin:keyLabel="ч" />
-        <Key
-            latin:keyLabel="ћ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-9.219%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="7.250%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="ѕ" />
-        <Key
-            latin:keyLabel="џ" />
-        <Key
-            latin:keyLabel="ц" />
-        <Key
-            latin:keyLabel="в" />
-        <Key
-            latin:keyLabel="б" />
-        <Key
-            latin:keyLabel="н" />
-        <Key
-            latin:keyLabel="м" />
-        <include
-            latin:keyboardLayout="@xml/kbd_row3_comma_period" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_spanish.xml b/java/res/xml-sw768dp/kbd_rows_spanish.xml
deleted file mode 100644
index c737f40..0000000
--- a/java/res/xml-sw768dp/kbd_rows_spanish.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="ñ" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-15.704%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_symbols.xml b/java/res/xml-sw768dp/kbd_rows_symbols.xml
deleted file mode 100644
index 987b10c..0000000
--- a/java/res/xml-sw768dp/kbd_rows_symbols.xml
+++ /dev/null
@@ -1,192 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_currency_key_styles" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_1"
-            latin:moreKeys="@string/more_keys_for_symbols_1" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_2"
-            latin:moreKeys="@string/more_keys_for_symbols_2" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_3"
-            latin:moreKeys="@string/more_keys_for_symbols_3" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_4"
-            latin:moreKeys="@string/more_keys_for_symbols_4" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_5"
-            latin:moreKeys="@string/more_keys_for_symbols_5" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_6"
-            latin:moreKeys="@string/more_keys_for_symbols_6" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_7"
-            latin:moreKeys="@string/more_keys_for_symbols_7" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_8"
-            latin:moreKeys="@string/more_keys_for_symbols_8" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_9"
-            latin:moreKeys="@string/more_keys_for_symbols_9" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_0"
-            latin:moreKeys="@string/more_keys_for_symbols_0" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-9.219%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel="#" />
-        <Key
-            latin:keyStyle="currencyKeyStyle" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_percent"
-            latin:moreKeys="@string/more_keys_for_symbols_percent" />
-        <Key
-            latin:keyLabel="&amp;" />
-        <Key
-            latin:keyLabel="*"
-            latin:moreKeys="@string/more_keys_for_star" />
-        <Key
-            latin:keyLabel="-"
-            latin:moreKeys="_,–,—" />
-        <Key
-            latin:keyLabel="+"
-            latin:moreKeys="@string/more_keys_for_plus" />
-        <Key
-            latin:keyLabel="("
-            latin:moreKeys="[,{,&lt;" />
-        <Key
-            latin:keyLabel=")"
-            latin:moreKeys="],},&gt;" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-15.704%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="&lt;"
-            latin:moreKeys="≤,«,‹" />
-        <Key
-            latin:keyLabel="&gt;"
-            latin:moreKeys="≥,»,›" />
-        <Key
-            latin:keyLabel="="
-            latin:moreKeys="≠,≈" />
-        <switch>
-            <case
-                latin:mode="url"
-            >
-                <Key
-                    latin:keyLabel="\'"
-                    latin:moreKeys="‘,’,‚,‛" />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=":" />
-            </default>
-        </switch>
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_semicolon"
-            latin:moreKeys="@string/more_keys_for_symbols_semicolon" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_comma"
-            latin:moreKeys="@string/more_keys_for_comma" />
-        <Key
-            latin:keyLabel="." />
-        <Key
-            latin:keyLabel="!"
-            latin:moreKeys="¡" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_question"
-            latin:moreKeys="@string/more_keys_for_symbols_question" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <switch>
-            <case
-                latin:hasSettingsKey="true"
-            >
-                <Key
-                    latin:keyStyle="settingsKeyStyle"
-                    latin:keyWidth="8.047%p" />
-            </case>
-        </switch>
-        <Key
-            latin:keyLabel="/"
-            latin:keyXPos="15.157%p" />
-        <Key
-            latin:keyLabel="\@" />
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="31.250%p"
-            latin:keyWidth="37.500%p" />
-        <!-- Note: DroidSans doesn't have double-high-reversed-quotation '\u201f' glyph. -->
-        <!-- latin:moreKeys="“,”,„,‟,«,»,‘,’,‚,‛" -->
-        <Key
-            latin:keyLabel="&quot;"
-            latin:moreKeys="“,”,«,»,‘,’,‚,‛" />
-        <Key
-            latin:keyLabel="_" />
-        <switch>
-            <case
-                latin:shortcutKeyEnabled="true"
-            >
-                <Key
-                    latin:keyStyle="shortcutKeyStyle"
-                    latin:keyXPos="-8.047%p"
-                    latin:keyWidth="fillRight" />
-            </case>
-        </switch>
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml b/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
deleted file mode 100644
index 9a9c3a2..0000000
--- a/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
+++ /dev/null
@@ -1,150 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_currency_key_styles" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <Key
-            latin:keyLabel="~" />
-        <Key
-            latin:keyLabel="`" />
-        <Key
-            latin:keyLabel="|" />
-        <Key
-            latin:keyLabel="•"
-            latin:moreKeys="@string/more_keys_for_bullet" />
-        <Key
-            latin:keyLabel="√" />
-        <Key
-            latin:keyLabel="π"
-            latin:moreKeys="Π" />
-        <Key
-            latin:keyLabel="÷" />
-        <Key
-            latin:keyLabel="×" />
-        <Key
-            latin:keyLabel="§"
-            latin:moreKeys="¶" />
-        <Key
-            latin:keyLabel="Δ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-9.219%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelOption="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyStyle="moreCurrency1KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency2KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency3KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency4KeyStyle" />
-        <Key
-            latin:keyLabel="^"
-            latin:moreKeys="↑,↓,←,→" />
-        <Key
-            latin:keyLabel="°"
-            latin:moreKeys="′,″" />
-        <Key
-            latin:keyLabel="±"
-            latin:moreKeys="∞" />
-        <Key
-            latin:keyLabel="{" />
-        <Key
-            latin:keyLabel="}" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
-            latin:keyXPos="-15.704%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="\\" />
-        <Key
-            latin:keyLabel="©" />
-        <Key
-            latin:keyLabel="®" />
-        <Key
-            latin:keyLabel="™" />
-        <Key
-            latin:keyLabel="℅" />
-        <Key
-            latin:keyLabel="[" />
-        <Key
-            latin:keyLabel="]" />
-        <Key
-            latin:keyLabel="¡" />
-        <Key
-            latin:keyLabel="¿" />
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillBoth" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <switch>
-            <case latin:hasSettingsKey="true">
-                <Key
-                    latin:keyStyle="settingsKeyStyle"
-                    latin:keyWidth="8.047%p" />
-            </case>
-        </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="31.250%p"
-            latin:keyWidth="37.500%p" />
-        <switch>
-            <case
-                latin:shortcutKeyEnabled="true"
-            >
-                <Key
-                    latin:keyStyle="shortcutKeyStyle"
-                    latin:keyXPos="-8.047%p"
-                    latin:keyWidth="fillRight" />
-            </case>
-        </switch>
-    </Row>
-</merge>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp/kbd_thai.xml
similarity index 77%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp/kbd_thai.xml
index e29d9ab..593ccbd 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp/kbd_thai.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="2.95%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp/kbd_thai_symbols.xml
similarity index 77%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp/kbd_thai_symbols.xml
index e29d9ab..e2e5f5d 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp/kbd_thai_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="2.95%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
similarity index 76%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
index e29d9ab..a1358d4 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
+    latin:rowHeight="20%p"
+    latin:verticalGap="2.95%p"
+    latin:touchPositionCorrectionData="@null"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sw768dp/key_settings.xml
similarity index 68%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml-sw768dp/key_settings.xml
index e29d9ab..0359a99 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp/key_settings.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,18 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
+    <switch>
+        <case
+            latin:clobberSettingsKey="false"
+        >
+            <Key
+                latin:keyStyle="settingsKeyStyle" />
+        </case>
+        <default>
+            <Spacer />
+        </default>
+    </switch>
+ </merge>
diff --git a/java/res/xml-sw768dp/key_styles_common.xml b/java/res/xml-sw768dp/key_styles_common.xml
new file mode 100644
index 0000000..f4a1a4e
--- /dev/null
+++ b/java/res/xml-sw768dp/key_styles_common.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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="hasShiftedLetterHintStyle"
+                latin:keyLabelFlags="hasShiftedLetterHint|shiftedLetterActivated" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="hasShiftedLetterHintStyle"
+                latin:keyLabelFlags="hasShiftedLetterHint" />
+        </default>
+    </switch>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetAutomaticShifted"
+        >
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKeyShifted"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOff" />
+        </case>
+        <case
+            latin:keyboardSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKeyShifted"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOn" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKey"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOff" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="deleteKeyStyle"
+        latin:code="@integer/key_delete"
+        latin:keyIcon="iconDeleteKey"
+        latin:keyActionFlags="isRepeatable|noKeyPreview"
+        latin:backgroundType="functional" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_enter" />
+    <key-style
+        latin:styleName="spaceKeyStyle"
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview" />
+    <!-- U+200C: ZERO WIDTH NON-JOINER
+         U+200D: ZERO WIDTH JOINER -->
+    <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:code="0x200C"
+        latin:keyIcon="iconZwnjKey"
+        latin:moreKeys="\@icon/zwjKey|&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        latin:keyActionFlags="noKeyPreview" />
+    <key-style
+        latin:styleName="smileyKeyStyle"
+        latin:keyLabel=":-)"
+        latin:keyOutputText=":-) "
+        latin:keyLabelFlags="hasPopupHint|preserveCase"
+        latin:moreKeys="@string/more_keys_for_smiley" />
+    <key-style
+        latin:styleName="shortcutKeyStyle"
+        latin:code="@integer/key_shortcut"
+        latin:keyIcon="iconShortcutKey"
+        latin:keyIconDisabled="iconDisabledShortcutKey"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="settingsKeyStyle"
+        latin:code="@integer/key_settings"
+        latin:keyIcon="iconSettingsKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:backgroundType="functional" />
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:navigatePrevious="true"
+        >
+            <key-style
+                latin:styleName="tabKeyStyle"
+                latin:code="@integer/key_action_previous"
+                latin:keyLabel="@string/label_tab_key"
+                latin:keyLabelFlags="fontNormal|preserveCase"
+                latin:backgroundType="functional" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="tabKeyStyle"
+                latin:code="@integer/key_tab"
+                latin:keyLabel="@string/label_tab_key"
+                latin:keyLabelFlags="fontNormal|preserveCase"
+                latin:backgroundType="functional" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="toSymbolKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
+        latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyLabelFlags="fontNormal|preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="toAlphaKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
+        latin:keyLabel="@string/label_to_alpha_key"
+        latin:keyLabelFlags="fontNormal|preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="toMoreSymbolKeyStyle"
+        latin:code="@integer/key_shift"
+        latin:keyLabel="@string/label_to_more_symbol_for_tablet_key"
+        latin:keyLabelFlags="fontNormal|preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="backFromMoreSymbolKeyStyle"
+        latin:code="@integer/key_shift"
+        latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyLabelFlags="fontNormal|preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="comKeyStyle"
+        latin:keyLabel="@string/keylabel_for_popular_domain"
+        latin:keyLabelFlags="fontNormal|hasPopupHint|preserveCase"
+        latin:keyOutputText="@string/keylabel_for_popular_domain"
+        latin:moreKeys="@string/more_keys_for_popular_domain" />
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml b/java/res/xml-sw768dp/keys_apostrophe_dash.xml
similarity index 85%
copy from java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
copy to java/res/xml-sw768dp/keys_apostrophe_dash.xml
index 9536e81..a53c1e4 100644
--- a/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
+++ b/java/res/xml-sw768dp/keys_apostrophe_dash.xml
@@ -33,16 +33,16 @@
         >
             <Key
                 latin:keyLabel="/"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel=":"
-                latin:moreKeys=":" />
+                latin:moreKeys=":"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </case>
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_apostrophe"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_apostrophe"
-                latin:moreKeys="@string/more_keys_for_apostrophe" />
+                latin:moreKeys="@string/more_keys_for_apostrophe"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
     <switch>
@@ -55,9 +55,9 @@
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_dash"
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_dash"
-                latin:moreKeys="@string/more_keys_for_dash" />
+                latin:moreKeys="@string/more_keys_for_dash"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row4.xml b/java/res/xml-sw768dp/row_qwerty4.xml
similarity index 73%
rename from java/res/xml-sw768dp/kbd_qwerty_row4.xml
rename to java/res/xml-sw768dp/row_qwerty4.xml
index e35e47d..90da21b 100644
--- a/java/res/xml-sw768dp/kbd_qwerty_row4.xml
+++ b/java/res/xml-sw768dp/row_qwerty4.xml
@@ -24,15 +24,8 @@
     <Row
         latin:keyWidth="8.047%p"
     >
-        <switch>
-            <case
-                latin:hasSettingsKey="true"
-            >
-                <Key
-                    latin:keyStyle="settingsKeyStyle"
-                    latin:keyWidth="8.047%p" />
-            </case>
-        </switch>
+        <include
+            latin:keyboardLayout="@xml/key_settings" />
         <Spacer
             latin:keyXPos="15.157%p"
             latin:keyWidth="0%p" />
@@ -57,9 +50,9 @@
                     >
                         <Key
                             latin:keyLabel=":"
-                            latin:keyLabelOption="hasUppercaseLetter"
                             latin:keyHintLabel="+"
-                            latin:moreKeys="+" />
+                            latin:moreKeys="+"
+                            latin:keyStyle="hasShiftedLetterHintStyle" />
                     </case>
                     <default>
                         <Key
@@ -76,27 +69,43 @@
                     <default>
                         <Key
                             latin:keyLabel="/"
-                            latin:keyLabelOption="hasUppercaseLetter"
                             latin:keyHintLabel="\@"
-                            latin:moreKeys="\@" />
+                            latin:moreKeys="\@"
+                            latin:keyStyle="hasShiftedLetterHintStyle" />
                     </default>
                 </switch>
             </default>
         </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="31.250%p"
-            latin:keyWidth="37.500%p" />
+        <switch>
+            <case
+                latin:languageCode="fa"
+            >
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="31.250%p"
+                    latin:keyWidth="29.453%p" />
+                <!-- U+200C: "" ZERO WIDTH NON-JOINER
+                     U+200D: "" ZERO WIDTH JOINER -->
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <default>
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="31.250%p"
+                    latin:keyWidth="37.500%p" />
+            </default>
+        </switch>
         <switch>
             <case
                 latin:languageCode="iw"
             >
                 <include
-                    latin:keyboardLayout="@xml/kbd_row3_comma_period" />
+                    latin:keyboardLayout="@xml/keys_comma_period" />
             </case>
             <default>
                 <include
-                    latin:keyboardLayout="@xml/kbd_row4_apostrophe_dash" />
+                    latin:keyboardLayout="@xml/keys_apostrophe_dash" />
             </default>
         </switch>
         <switch>
diff --git a/java/res/xml-sw768dp/rowkeys_thai_digits.xml b/java/res/xml-sw768dp/rowkeys_thai_digits.xml
new file mode 100644
index 0000000..5122830
--- /dev/null
+++ b/java/res/xml-sw768dp/rowkeys_thai_digits.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0E51: "๑" THAI DIGIT ONE -->
+    <Key
+        latin:keyLabel="&#x0E51;" />
+    <!-- U+0E52: "๒" THAI DIGIT TWO -->
+    <Key
+        latin:keyLabel="&#x0E52;" />
+    <!-- U+0E53: "๓" THAI DIGIT THREE -->
+    <Key
+        latin:keyLabel="&#x0E53;" />
+    <!-- U+0E54: "๔" THAI DIGIT FOUR -->
+    <Key
+        latin:keyLabel="&#x0E54;" />
+    <!-- U+0E55: "๕" THAI DIGIT FIVE -->
+    <Key
+        latin:keyLabel="&#x0E55;" />
+    <!-- U+0E56: "๖" THAI DIGIT SIX -->
+    <Key
+        latin:keyLabel="&#x0E56;" />
+    <!-- U+0E57: "๗" THAI DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x0E57;" />
+    <!-- U+0E58: "๘" THAI DIGIT EIGHT -->
+    <Key
+        latin:keyLabel="&#x0E58;" />
+    <!-- U+0E59: "๙" THAI DIGIT NINE -->
+    <Key
+        latin:keyLabel="&#x0E59;" />
+    <!-- U+0E50: "๐" THAI DIGIT ZERO -->
+    <Key
+        latin:keyLabel="&#x0E50;" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_10_10_7_symbols.xml b/java/res/xml-sw768dp/rows_10_10_7_symbols.xml
new file mode 100644
index 0000000..1d61d99
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_10_10_7_symbols.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="10.167%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols3" />
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.xml b/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.xml
new file mode 100644
index 0000000..d22f833
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="10.167%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols_shift4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_arabic.xml b/java/res/xml-sw768dp/rows_arabic.xml
new file mode 100644
index 0000000..eaa64e5
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_arabic.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic3"
+            latin:keyXPos="13.829%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_azerty.xml b/java/res/xml-sw768dp/rows_azerty.xml
new file mode 100644
index 0000000..1fc1a06
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_azerty.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="10.167%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_bulgarian.xml b/java/res/xml-sw768dp/rows_bulgarian.xml
new file mode 100644
index 0000000..cb05fd5
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_bulgarian.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.186%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.186%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.125%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_east_slavic.xml b/java/res/xml-sw768dp/rows_east_slavic.xml
new file mode 100644
index 0000000..480881b
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_east_slavic.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+        <Key
+            latin:keyLabel="&#x044A;" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_farsi.xml b/java/res/xml-sw768dp/rows_farsi.xml
new file mode 100644
index 0000000..28ea6ff
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_farsi.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_georgian.xml b/java/res/xml-sw768dp/rows_georgian.xml
new file mode 100644
index 0000000..3805b5d
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_georgian.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth"/>
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_greek.xml b/java/res/xml-sw768dp/rows_greek.xml
new file mode 100644
index 0000000..c0b38dc
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_greek.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/key_greek_semicolon" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+   <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_hebrew.xml b/java/res/xml-sw768dp/rows_hebrew.xml
new file mode 100644
index 0000000..476071a
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_hebrew.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/keys_apostrophe_dash" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="10.167%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+        </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew3"
+            latin:keyXPos="13.829%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_hindi.xml b/java/res/xml-sw768dp/rows_hindi.xml
new file mode 100644
index 0000000..86e925f
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_hindi.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.333%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.186%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.040%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_nordic.xml b/java/res/xml-sw768dp/rows_nordic.xml
new file mode 100644
index 0000000..fbee1c3
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_nordic.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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.227%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.227%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_phone_shift.xml b/java/res/xml-sw768dp/rows_number_normal.xml
similarity index 63%
copy from java/res/xml-sw768dp/kbd_phone_shift.xml
copy to java/res/xml-sw768dp/rows_number_normal.xml
index 46f67d3..2eeb6c9 100644
--- a/java/res/xml-sw768dp/kbd_phone_shift.xml
+++ b/java/res/xml-sw768dp/rows_number_normal.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,18 +18,13 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="13.250%p"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
     <Row>
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
             latin:keyLabel="-"
@@ -41,49 +36,65 @@
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:code="44"
-            latin:keyLabel="@string/label_pause_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
+            latin:keyLabel="."
+            latin:keyStyle="numKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:keyStyle="num1KeyStyle"
+            latin:keyLabel="1"
+            latin:keyStyle="numKeyStyle"
             latin:keyXPos="43.125%p" />
         <Key
-            latin:keyStyle="num2KeyStyle" />
+            latin:keyLabel="2"
+            latin:keyStyle="numKeyStyle" />
         <Key
-            latin:keyStyle="num3KeyStyle" />
+            latin:keyLabel="3"
+            latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyXPos="-11.172%p"
             latin:keyWidth="fillRight" />
     </Row>
     <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="13.829%p" />
         <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel=","
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="13.829%p"
+            latin:keyStyle="numStarKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:keyLabel="."
+            latin:keyLabel="/"
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="8.047%p" />
+        <switch>
+            <case
+                latin:mode="time|datetime"
+            >
+                <Key
+                    latin:keyLabel=","
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:moreKeys="@string/more_keys_for_am_pm"
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="8.047%p" />
+            </case>
+            <default>
+                <Key
+                    latin:keyLabel=","
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="8.047%p" />
+            </default>
+        </switch>
         <Key
-            latin:code="59"
-            latin:keyLabel="@string/label_wait_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="num4KeyStyle"
+            latin:keyLabel="4"
+            latin:keyStyle="numKeyStyle"
             latin:keyXPos="43.125%p" />
         <Key
-            latin:keyStyle="num5KeyStyle" />
+            latin:keyLabel="5"
+            latin:keyStyle="numKeyStyle" />
         <Key
-            latin:keyStyle="num6KeyStyle" />
+            latin:keyLabel="6"
+            latin:keyStyle="numKeyStyle" />
         <Key
-            latin:keyStyle="returnKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyXPos="-11.172%p"
             latin:keyWidth="fillRight" />
     </Row>
@@ -99,50 +110,57 @@
             latin:keyLabel=")"
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="8.047%p" />
+        <switch>
+            <case
+                latin:mode="time|datetime"
+            >
+                <Key
+                    latin:keyLabel=":"
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="8.047%p" />
+            </case>
+            <default>
+                <Key
+                    latin:keyLabel="="
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="8.047%p" />
+            </default>
+        </switch>
         <Key
-            latin:keyLabel="N"
+            latin:keyLabel="7"
             latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="num7KeyStyle"
             latin:keyXPos="43.125%p" />
         <Key
-            latin:keyStyle="num8KeyStyle" />
+            latin:keyLabel="8"
+            latin:keyStyle="numKeyStyle" />
         <Key
-            latin:keyStyle="num9KeyStyle" />
+            latin:keyLabel="9"
+            latin:keyStyle="numKeyStyle" />
         <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
         <Spacer
             latin:keyWidth="0%p" />
     </Row>
     <Row>
-        <switch>
-            <case latin:hasSettingsKey="true">
-                <Key
-                    latin:keyStyle="settingsKeyStyle"
-                    latin:keyWidth="8.047%p" />
-            </case>
-            <default>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="8.047%p" />
-            </default>
-        </switch>
+        <include
+            latin:keyboardLayout="@xml/key_settings"
+            latin:keyWidth="8.047%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyStyle="spaceKeyStyle"
             latin:keyXPos="13.829%p"
             latin:keyWidth="24.140%p" />
         <Key
             latin:keyStyle="numStarKeyStyle"
             latin:keyXPos="43.125%p" />
         <Key
-            latin:keyStyle="num0KeyStyle" />
+            latin:keyLabel="0"
+            latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyLabel="#"
             latin:keyStyle="numKeyStyle" />
         <switch>
             <case
                 latin:shortcutKeyEnabled="true"
-            >
+                    >
                 <Key
                     latin:keyStyle="shortcutKeyStyle"
                     latin:keyXPos="-8.047%p"
@@ -155,4 +173,4 @@
             </default>
         </switch>
     </Row>
-</Keyboard>
+</merge>
diff --git a/java/res/xml-sw768dp/rows_number_password.xml b/java/res/xml-sw768dp/rows_number_password.xml
new file mode 100644
index 0000000..8acfac6
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_number_password.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row>
+        <Key
+            latin:keyStyle="numTabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <Key
+            latin:keyStyle="num1KeyStyle"
+            latin:keyXPos="32.076%p" />
+        <Key
+            latin:keyStyle="num2KeyStyle" />
+        <Key
+            latin:keyStyle="num3KeyStyle" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-11.172%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="32.076%p" />
+        <Key
+            latin:keyStyle="num4KeyStyle" />
+        <Key
+            latin:keyStyle="num5KeyStyle" />
+        <Key
+            latin:keyStyle="num6KeyStyle" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-11.172%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="32.076%p" />
+        <Key
+            latin:keyStyle="num7KeyStyle" />
+        <Key
+            latin:keyStyle="num8KeyStyle" />
+        <Key
+            latin:keyStyle="num9KeyStyle" />
+        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
+        <Spacer
+            latin:keyWidth="0%p" />
+    </Row>
+    <Row>
+        <Spacer
+            latin:keyXPos="32.076%p" />
+        <Key
+            latin:keyStyle="num0KeyStyle" />
+        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
+        <Spacer
+            latin:keyWidth="0%p" />
+    </Row>
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_phone_shift.xml b/java/res/xml-sw768dp/rows_phone.xml
similarity index 75%
rename from java/res/xml-sw768dp/kbd_phone_shift.xml
rename to java/res/xml-sw768dp/rows_phone.xml
index 46f67d3..216fbed 100644
--- a/java/res/xml-sw768dp/kbd_phone_shift.xml
+++ b/java/res/xml-sw768dp/rows_phone.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,18 +18,17 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
+        latin:keyboardLayout="@xml/key_styles_common" />
     <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
+        latin:keyboardLayout="@xml/key_styles_number" />
     <Row>
         <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyStyle="numTabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
             latin:keyLabel="-"
@@ -41,9 +40,7 @@
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:code="44"
-            latin:keyLabel="@string/label_pause_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
+            latin:keyStyle="numPauseKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
             latin:keyStyle="num1KeyStyle"
@@ -58,9 +55,9 @@
             latin:keyWidth="fillRight" />
     </Row>
     <Row>
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="11.172%p" />
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="13.829%p" />
         <Key
             latin:keyLabel=","
             latin:keyStyle="numKeyStyle"
@@ -71,9 +68,7 @@
             latin:keyStyle="numKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:code="59"
-            latin:keyLabel="@string/label_wait_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
+            latin:keyStyle="numWaitKeyStyle"
             latin:keyWidth="8.047%p" />
         <Key
             latin:keyStyle="num4KeyStyle"
@@ -83,7 +78,7 @@
         <Key
             latin:keyStyle="num6KeyStyle" />
         <Key
-            latin:keyStyle="returnKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyXPos="-11.172%p"
             latin:keyWidth="fillRight" />
     </Row>
@@ -115,20 +110,11 @@
             latin:keyWidth="0%p" />
     </Row>
     <Row>
-        <switch>
-            <case latin:hasSettingsKey="true">
-                <Key
-                    latin:keyStyle="settingsKeyStyle"
-                    latin:keyWidth="8.047%p" />
-            </case>
-            <default>
-                <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-                <Spacer
-                    latin:keyWidth="8.047%p" />
-            </default>
-        </switch>
+        <include
+            latin:keyboardLayout="@xml/key_settings"
+            latin:keyWidth="8.047%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyStyle="spaceKeyStyle"
             latin:keyXPos="13.829%p"
             latin:keyWidth="24.140%p" />
         <Key
@@ -155,4 +141,4 @@
             </default>
         </switch>
     </Row>
-</Keyboard>
+</merge>
diff --git a/java/res/xml-sw768dp/rows_qwerty.xml b/java/res/xml-sw768dp/rows_qwerty.xml
new file mode 100644
index 0000000..edfb401
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_qwerty.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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth"/>
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_qwertz.xml b/java/res/xml-sw768dp/rows_qwertz.xml
new file mode 100644
index 0000000..7e75506
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_qwertz.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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth"/>
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_south_slavic.xml b/java/res/xml-sw768dp/rows_south_slavic.xml
new file mode 100644
index 0000000..7ea0ff4
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_south_slavic.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.000%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_spanish.xml b/java/res/xml-sw768dp/rows_spanish.xml
new file mode 100644
index 0000000..fc3388d
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_spanish.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth"/>
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="10.167%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_spanish2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_symbols.xml b/java/res/xml-sw768dp/rows_symbols.xml
new file mode 100644
index 0000000..ed2df18
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_symbols.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols3" />
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_symbols4.xml b/java/res/xml-sw768dp/rows_symbols4.xml
new file mode 100644
index 0000000..19b36d6
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_symbols4.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/key_settings" />
+        <Key
+            latin:keyLabel="/"
+            latin:keyXPos="15.157%p" />
+        <Key
+            latin:keyLabel="\@" />
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyXPos="31.250%p"
+            latin:keyWidth="37.500%p" />
+        <Key
+            latin:keyLabel="&quot;"
+            latin:moreKeys="@string/more_keys_for_tablet_double_quote" />
+        <Key
+            latin:keyLabel="_" />
+        <switch>
+            <case
+                latin:shortcutKeyEnabled="true"
+            >
+                <Key
+                    latin:keyStyle="shortcutKeyStyle"
+                    latin:keyXPos="-8.047%p"
+                    latin:keyWidth="fillRight" />
+            </case>
+        </switch>
+    </Row>
+</merge>
diff --git a/java/res/xml-sw768dp/rows_symbols_shift.xml b/java/res/xml-sw768dp/rows_symbols_shift.xml
new file mode 100644
index 0000000..30e1df0
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_symbols_shift.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols_shift4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_symbols_shift4.xml b/java/res/xml-sw768dp/rows_symbols_shift4.xml
new file mode 100644
index 0000000..8e0071f
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_symbols_shift4.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/key_settings" />
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyXPos="31.250%p"
+            latin:keyWidth="37.500%p" />
+        <switch>
+            <case
+                latin:shortcutKeyEnabled="true"
+            >
+                <Key
+                    latin:keyStyle="shortcutKeyStyle"
+                    latin:keyXPos="-8.047%p"
+                    latin:keyWidth="fillRight" />
+            </case>
+        </switch>
+    </Row>
+</merge>
diff --git a/java/res/xml-sw768dp/rows_thai.xml b/java/res/xml-sw768dp/rows_thai.xml
new file mode 100644
index 0000000..cc77f8b
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_thai.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.079%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai1"
+            latin:keyXPos="11.508%p"/>
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth"/>
+    </Row>
+    <Row
+        latin:keyWidth="7.079%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai2" />
+    </Row>
+    <Row
+        latin:keyWidth="7.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai3" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.181%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p"/>
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai4" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_thai_symbols.xml b/java/res/xml-sw768dp/rows_thai_symbols.xml
new file mode 100644
index 0000000..20f22bc
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_thai_symbols.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai_digits"
+            latin:keyXPos="7.969%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols3" />
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols4" />
+</merge>
diff --git a/java/res/xml-sw768dp/rows_thai_symbols_shift.xml b/java/res/xml-sw768dp/rows_thai_symbols_shift.xml
new file mode 100644
index 0000000..467dd19
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_thai_symbols_shift.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai_digits"
+            latin:keyXPos="7.969%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.282%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.969%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
+    </Row>
+    <Row
+        latin:keyWidth="8.125%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="11.172%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyXPos="-13.750%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols_shift4" />
+</merge>
diff --git a/java/res/xml-th/keyboard_set.xml b/java/res/xml-th/keyboard_set.xml
new file mode 100644
index 0000000..99e81b8
--- /dev/null
+++ b/java/res/xml-th/keyboard_set.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_thai"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_thai"
+        latin:enableProximityCharsCorrection="true" />
+    <!-- On these shifted alphabet layouts the proximity characters correction should be disabled
+         because the letters on these layouts aren't the ones in different case of the above
+         unshifted layouts. -->
+    <Element
+        latin:elementName="alphabetManualShifted"
+        latin:elementKeyboard="@xml/kbd_thai" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_thai" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_thai" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_thai_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_thai_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-tr/kbd_qwerty.xml b/java/res/xml-tr/kbd_qwerty.xml
deleted file mode 100644
index d2c38f6..0000000
--- a/java/res/xml-tr/kbd_qwerty.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="tr"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</Keyboard>
diff --git a/java/res/xml-tr/keyboard_set.xml b/java/res/xml-tr/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-tr/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-uk/keyboard_set.xml b/java/res/xml-uk/keyboard_set.xml
new file mode 100644
index 0000000..959f644
--- /dev/null
+++ b/java/res/xml-uk/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-vi/keyboard_set.xml b/java/res/xml-vi/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml-vi/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-zz-rQY/keyboard_set.xml b/java/res/xml-zz-rQY/keyboard_set.xml
new file mode 100644
index 0000000..6fa9701
--- /dev/null
+++ b/java/res/xml-zz-rQY/keyboard_set.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:disableShortcutKey="true" >
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_arabic.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_arabic.xml
index 3195d5b..ce5f30b 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_arabic.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_arabic" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_azerty.xml
similarity index 89%
rename from java/res/xml-hu/kbd_qwerty.xml
rename to java/res/xml/kbd_azerty.xml
index 3195d5b..7bafe5b 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_azerty.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_azerty" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_azerty_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_azerty_symbols.xml
index e29d9ab..7e075df 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_azerty_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_azerty_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_azerty_symbols_shift.xml
index e29d9ab..25db3c8 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_azerty_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_bulgarian.xml
similarity index 83%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_bulgarian.xml
index 3195d5b..a651991 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_bulgarian.xml
@@ -2,9 +2,9 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License");
+** 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
 **
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_bulgarian" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_east_slavic.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_east_slavic.xml
index 3195d5b..3bc2339 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_east_slavic.xml
@@ -4,7 +4,7 @@
 **
 ** Copyright 2011, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License");
+** 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
 **
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_east_slavic" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_farsi.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_farsi.xml
index e29d9ab..1af4e61 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_farsi.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_farsi" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_georgian.xml
similarity index 83%
rename from java/res/xml-sv/kbd_qwerty.xml
rename to java/res/xml/kbd_georgian.xml
index e29d9ab..2dc6bf0 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_georgian.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_georgian" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_greek.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_greek.xml
index e29d9ab..7056efb 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_greek.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_greek" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_hebrew.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_hebrew.xml
index 3195d5b..74836f3 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_hebrew.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_hebrew" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_hebrew_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_hebrew_symbols.xml
index e29d9ab..7e075df 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_hebrew_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_hebrew_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_hebrew_symbols_shift.xml
index e29d9ab..25db3c8 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_hebrew_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_hindi.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_hindi.xml
index e29d9ab..0e69e3a 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_hindi.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_hindi" />
 </Keyboard>
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
deleted file mode 100644
index 453b05d..0000000
--- a/java/res/xml/kbd_key_styles.xml
+++ /dev/null
@@ -1,223 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- Base key style for the key which may have settings or tab key as popup key. -->
-    <switch>
-        <case
-            latin:clobberSettingsKey="true"
-        >
-            <key-style
-                latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="@string/more_keys_for_f1"
-                latin:backgroundType="functional" />
-        </case>
-        <!-- clobberSettingsKey="false -->
-        <case
-            latin:hasSettingsKey="false"
-        >
-            <key-style
-                latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="@string/more_keys_for_f1_settings"
-                latin:backgroundType="functional" />
-        </case>
-        <!-- clobberSettingsKey="false" hasSettingsKey="true" -->
-        <case
-            latin:navigateAction="true"
-        >
-            <key-style
-                latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="@string/more_keys_for_f1_navigate"
-                latin:backgroundType="functional" />
-        </case>
-        <!-- clobberSettingsKey="false" and hasSettingsKey="true" navigateAction="false" -->
-        <default>
-            <key-style
-                latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="@string/more_keys_for_f1"
-                latin:backgroundType="functional" />
-        </default>
-    </switch>
-    <!-- Functional key styles -->
-    <key-style
-        latin:styleName="shiftKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyIcon="iconShiftKey"
-        latin:keyIconShifted="iconShiftedShiftKey"
-        latin:backgroundType="sticky" />
-    <key-style
-        latin:styleName="deleteKeyStyle"
-        latin:code="@integer/key_delete"
-        latin:keyIcon="iconDeleteKey"
-        latin:backgroundType="functional"
-        latin:isRepeatable="true" />
-    <!-- Return key style -->
-    <switch>
-        <case
-            latin:mode="im"
-        >
-            <!-- Smiley key. -->
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:keyLabel=":-)"
-                latin:keyOutputText=":-) "
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="@string/more_keys_for_smiley"
-                latin:maxMoreKeysColumn="5"
-                latin:backgroundType="functional" />
-        </case>
-        <case
-            latin:imeAction="actionGo"
-        >
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:code="@integer/key_return"
-                latin:keyLabel="@string/label_go_key"
-                latin:keyLabelOption="autoXScale"
-                latin:backgroundType="action" />
-        </case>
-        <case
-            latin:imeAction="actionNext"
-        >
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:code="@integer/key_return"
-                latin:keyLabel="@string/label_next_key"
-                latin:keyLabelOption="autoXScale"
-                latin:backgroundType="action" />
-        </case>
-        <case
-            latin:imeAction="actionDone"
-        >
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:code="@integer/key_return"
-                latin:keyLabel="@string/label_done_key"
-                latin:keyLabelOption="autoXScale"
-                latin:backgroundType="action" />
-        </case>
-        <case
-            latin:imeAction="actionSend"
-        >
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:code="@integer/key_return"
-                latin:keyLabel="@string/label_send_key"
-                latin:keyLabelOption="autoXScale"
-                latin:backgroundType="action" />
-        </case>
-        <case
-            latin:imeAction="actionSearch"
-        >
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:code="@integer/key_return"
-                latin:keyIcon="iconSearchKey"
-                latin:backgroundType="action" />
-        </case>
-        <default>
-            <key-style
-                latin:styleName="returnKeyStyle"
-                latin:code="@integer/key_return"
-                latin:keyIcon="iconReturnKey"
-                latin:backgroundType="functional" />
-        </default>
-    </switch>
-    <key-style
-        latin:styleName="spaceKeyStyle"
-        latin:code="@integer/key_space"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="shortcutKeyStyle"
-        latin:code="@integer/key_shortcut"
-        latin:keyIcon="iconShortcutKey"
-        latin:parentStyle="f1PopupStyle" />
-    <key-style
-        latin:styleName="settingsKeyStyle"
-        latin:code="@integer/key_settings"
-        latin:keyIcon="iconSettingsKey"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="tabKeyStyle"
-        latin:code="@integer/key_tab"
-        latin:keyIcon="iconTabKey"
-        latin:keyIconPreview="iconPreviewTabKey"
-        latin:backgroundType="functional" />
-    <!-- Note: This key style is not for functional tab key. This is used for the tab key which is
-         laid out as normal letter key. -->
-    <key-style
-        latin:styleName="nonSpecialBackgroundTabKeyStyle"
-        latin:code="@integer/key_tab"
-        latin:keyIcon="iconTabKey"
-        latin:keyIconPreview="iconPreviewTabKey" />
-    <switch>
-        <!-- When this qwerty keyboard has no shortcut keys but shortcut key is enabled, then symbol
-             keyboard will have a shortcut key. That means we should use
-             iconToSymbolKeyWithShortcutKey here. -->
-        <case
-            latin:shortcutKeyEnabled="true"
-            latin:hasShortcutKey="false"
-        >
-            <key-style
-                latin:styleName="toSymbolKeyStyle"
-                latin:code="@integer/key_switch_alpha_symbol"
-                latin:keyIcon="iconShortcutForLabel"
-                latin:keyLabel="@string/label_to_symbol_with_microphone_key"
-                latin:keyLabelOption="withIconRight"
-                latin:backgroundType="functional" />
-        </case>
-        <default>
-            <key-style
-                latin:styleName="toSymbolKeyStyle"
-                latin:code="@integer/key_switch_alpha_symbol"
-                latin:keyLabel="@string/label_to_symbol_key"
-                latin:backgroundType="functional" />
-        </default>
-    </switch>
-    <key-style
-        latin:styleName="toAlphaKeyStyle"
-        latin:code="@integer/key_switch_alpha_symbol"
-        latin:keyLabel="@string/label_to_alpha_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toMoreSymbolKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyLabel="@string/label_to_more_symbol_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="backFromMoreSymbolKeyStyle"
-        latin:code="@integer/key_shift"
-        latin:keyLabel="@string/label_to_symbol_key"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="punctuationKeyStyle"
-        latin:keyLabel="."
-        latin:keyHintLabel="@string/keyhintlabel_for_punctuation"
-        latin:keyLabelOption="hasPopupHint"
-        latin:moreKeys="@string/more_keys_for_punctuation"
-        latin:maxMoreKeysColumn="@integer/mini_keyboard_column_for_punctuation"
-        latin:backgroundType="functional" />
-</merge>
diff --git a/java/res/xml/kbd_mini_keyboard_template.xml b/java/res/xml/kbd_more_keys_keyboard_template.xml
similarity index 95%
rename from java/res/xml/kbd_mini_keyboard_template.xml
rename to java/res/xml/kbd_more_keys_keyboard_template.xml
index ad6cf51..8e977c5 100644
--- a/java/res/xml/kbd_mini_keyboard_template.xml
+++ b/java/res/xml/kbd_more_keys_keyboard_template.xml
@@ -21,6 +21,6 @@
 <Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="10%p"
     latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/miniKeyboardStyle"
+    style="?attr/moreKeysKeyboardStyle"
     >
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_nordic.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_nordic.xml
index 3195d5b..a2196c9 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_nordic.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_nordic" />
 </Keyboard>
diff --git a/java/res/xml/kbd_number.xml b/java/res/xml/kbd_number.xml
index 38dd6bf..8b0deea 100644
--- a/java/res/xml/kbd_number.xml
+++ b/java/res/xml/kbd_number.xml
@@ -23,5 +23,5 @@
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_number" />
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml/kbd_phone.xml b/java/res/xml/kbd_phone.xml
index b550f17..91637b6 100644
--- a/java/res/xml/kbd_phone.xml
+++ b/java/res/xml/kbd_phone.xml
@@ -23,5 +23,5 @@
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_phone" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml/kbd_phone_shift.xml b/java/res/xml/kbd_phone_symbols.xml
similarity index 92%
rename from java/res/xml/kbd_phone_shift.xml
rename to java/res/xml/kbd_phone_symbols.xml
index eea823f..7f59a85 100644
--- a/java/res/xml/kbd_phone_shift.xml
+++ b/java/res/xml/kbd_phone_symbols.xml
@@ -23,5 +23,5 @@
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
+        latin:keyboardLayout="@xml/rows_phone_symbols" />
 </Keyboard>
diff --git a/java/res/xml/kbd_qwerty.xml b/java/res/xml/kbd_qwerty.xml
index 40917b9..2f49b94 100644
--- a/java/res/xml/kbd_qwerty.xml
+++ b/java/res/xml/kbd_qwerty.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="en_GB,en_US"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_qwerty" />
 </Keyboard>
diff --git a/java/res/xml/kbd_qwerty_f1.xml b/java/res/xml/kbd_qwerty_f1.xml
deleted file mode 100644
index 83b6ecc..0000000
--- a/java/res/xml/kbd_qwerty_f1.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:hasSettingsKey="false"
-            latin:navigateAction="false"
-        >
-            <switch>
-                <case
-                    latin:mode="url"
-                >
-                    <Key
-                        latin:keyLabel="/"
-                        latin:keyStyle="f1PopupStyle" />
-                </case>
-                <case
-                    latin:mode="email"
-                >
-                    <Key
-                        latin:keyLabel="\@"
-                        latin:keyStyle="f1PopupStyle" />
-                </case>
-                <case
-                    latin:hasShortcutKey="true"
-                >
-                    <Key
-                        latin:keyStyle="shortcutKeyStyle" />
-                </case>
-                <!-- latin:hasShortcutKey="false" -->
-                <default>
-                    <Key
-                        latin:keyLabel="@string/keylabel_for_comma"
-                        latin:keyStyle="f1PopupStyle" />
-                </default>
-            </switch>
-        </case>
-        <!-- hasSettingsKey="true" or navigateAction="true" -->
-        <default>
-            <switch>
-                <case
-                    latin:mode="url"
-                >
-                    <Key
-                        latin:keyLabel="/"
-                        latin:keyWidth="9.2%p"
-                        latin:keyStyle="f1PopupStyle" />
-                </case>
-                <case
-                    latin:mode="email"
-                >
-                    <Key
-                        latin:keyLabel="\@"
-                        latin:keyWidth="9.2%p"
-                        latin:keyStyle="f1PopupStyle" />
-                </case>
-                <case
-                    latin:hasShortcutKey="true"
-                >
-                    <Key
-                        latin:keyStyle="shortcutKeyStyle"
-                        latin:keyWidth="9.2%p" />
-                </case>
-                <!-- hasShortcutKey="false" -->
-                <default>
-                    <Key
-                        latin:keyLabel="@string/keylabel_for_comma"
-                        latin:keyWidth="9.2%p"
-                        latin:keyStyle="f1PopupStyle" />
-                </default>
-            </switch>
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml/kbd_qwerty_row1.xml b/java/res/xml/kbd_qwerty_row1.xml
deleted file mode 100644
index e8e8d1b..0000000
--- a/java/res/xml/kbd_qwerty_row1.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:keyHintLabel="1"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:keyHintLabel="2"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:keyHintLabel="3"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:keyHintLabel="4"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:keyHintLabel="6"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:keyHintLabel="7"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:keyHintLabel="8"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:keyHintLabel="9"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:keyHintLabel="0"
-            latin:moreKeys="@string/more_keys_for_p"
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml/kbd_qwerty_row2.xml b/java/res/xml/kbd_qwerty_row2.xml
deleted file mode 100644
index 8986780..0000000
--- a/java/res/xml/kbd_qwerty_row2.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a"
-            latin:keyXPos="5%p" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <!-- Here is 5%p space -->
-    </Row>
-</merge>
diff --git a/java/res/xml/kbd_qwerty_row4.xml b/java/res/xml/kbd_qwerty_row4.xml
deleted file mode 100644
index eb1e9b8..0000000
--- a/java/res/xml/kbd_qwerty_row4.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <switch>
-            <case
-                latin:hasSettingsKey="false"
-                latin:navigateAction="false"
-            >
-                <Key
-                    latin:keyStyle="toSymbolKeyStyle"
-                    latin:keyWidth="15%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_f1" />
-                <Key
-                    latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="50%p" />
-                <Key
-                    latin:keyStyle="punctuationKeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </case>
-            <!-- hasSettingsKey="true" or navigateAction="true" -->
-            <default>
-                <Key
-                    latin:keyStyle="toSymbolKeyStyle"
-                    latin:keyWidth="13.75%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_settings_or_tab" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_f1" />
-                <Key
-                    latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="35.83%p" />
-                <Key
-                    latin:keyStyle="punctuationKeyStyle"
-                    latin:keyWidth="9.2%p" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </default>
-        </switch>
-    </Row>
-</merge>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_qwertz.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_qwertz.xml
index 3195d5b..9f7e901 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_qwertz.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_qwertz" />
 </Keyboard>
diff --git a/java/res/xml/kbd_rows_arabic.xml b/java/res/xml/kbd_rows_arabic.xml
deleted file mode 100644
index dd5123e..0000000
--- a/java/res/xml/kbd_rows_arabic.xml
+++ /dev/null
@@ -1,189 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <!-- \u0636: ARABIC LETTER DAD -->
-        <Key
-            latin:keyLabel="ض"
-            latin:keyHintLabel="1"
-            latin:moreKeys="1,١" />
-        <!-- \u0635: ARABIC LETTER SAD -->
-        <Key
-            latin:keyLabel="ص"
-            latin:keyHintLabel="2"
-            latin:moreKeys="2,٢" />
-        <!-- \u0642: ARABIC LETTER QAF
-             \u06a8: ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
-        <Key
-            latin:keyLabel="ق"
-            latin:keyHintLabel="3"
-            latin:moreKeys="3,٣,\u06a8" />
-        <!-- \u0641: ARABIC LETTER FEH
-             \u06a4: ARABIC LETTER VEH
-             \u06a2: ARABIC LETTER FEH WITH DOT MOVED BELOW
-             \u06a5: ARABIC LETTER FEH WITH THREE DOTS BELOW -->
-        <Key
-            latin:keyLabel="ف"
-            latin:keyHintLabel="4"
-            latin:moreKeys="4,٤,\u06a4,\u06a2,\u06a5" />
-        <!-- \u063a: ARABIC LETTER GHAIN -->
-        <Key
-            latin:keyLabel="غ"
-            latin:keyHintLabel="5"
-            latin:moreKeys="5,٥" />
-        <!-- \u0639: ARABIC LETTER AIN -->
-        <Key
-            latin:keyLabel="ع"
-            latin:keyHintLabel="6"
-            latin:moreKeys="6,٦" />
-        <!-- \u0647: ARABIC LETTER HEH
-             \ufeeb: ARABIC LETTER HEH INITIAL FORM
-             \u0647\u0640: ARABIC LETTER HEH + Zero width joiner -->
-        <Key
-            latin:keyLabel="ه"
-            latin:keyHintLabel="7"
-            latin:moreKeys="7,٧,\ufeeb|\u0647\u200D" />
-        <!-- \u062e: ARABIC LETTER KHAH -->
-        <Key
-            latin:keyLabel="خ"
-            latin:keyHintLabel="8"
-            latin:moreKeys="8,٨" />
-        <!-- \u062d: ARABIC LETTER HAH -->
-        <Key
-            latin:keyLabel="ح"
-            latin:keyHintLabel="9"
-            latin:moreKeys="9,٩" />
-        <!-- \u062c: ARABIC LETTER JEEM
-             \u0686: ARABIC LETTER TCHEH -->
-        <Key
-            latin:keyLabel="ج"
-            latin:keyHintLabel="0"
-            latin:moreKeys="0,٠,\u0686"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <!-- \u0634: ARABIC LETTER SHEEN
-             \u069c: ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-        <Key
-            latin:keyLabel="ش"
-            latin:moreKeys="ڜ" />
-        <!-- \u0633: ARABIC LETTER SEEN -->
-        <Key
-            latin:keyLabel="س" />
-        <!-- \u064a: ARABIC LETTER YEH
-             \u0626: ARABIC LETTER YEH WITH HAMZA ABOVE
-             \u0649: ARABIC LETTER ALEF MAKSURA -->
-        <Key
-            latin:keyLabel="ي"
-            latin:moreKeys="\u0626,\u0649" />
-        <!-- \u0628: ARABIC LETTER BEH
-             \u067e: ARABIC LETTER PEH -->
-        <Key
-            latin:keyLabel="ب"
-            latin:moreKeys="پ" />
-        <!-- \u0644: ARABIC LETTER LAM
-             \ufefb: ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
-             \u0627: ARABIC LETTER ALEF
-             \ufef7: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
-             \u0623: ARABIC LETTER ALEF WITH HAMZA ABOVE
-             \ufef9: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
-             \u0625: ARABIC LETTER ALEF WITH HAMZA BELOW
-             \ufef5: ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
-             \u0622: ARABIC LETTER ALEF WITH MADDA ABOVE -->
-        <Key
-            latin:keyLabel="ل"
-            latin:moreKeys="\ufefb|\u0644\u0627,\ufef7|\u0644\u0623,\ufef9|\u0644\u0625,\ufef5|\u0644\u0622" />
-        <!-- \u0627: ARABIC LETTER ALEF
-             \u0621: ARABIC LETTER HAMZA
-             \u0671: ARABIC LETTER ALEF WASLA
-             \u0623: ARABIC LETTER ALEF WITH HAMZA ABOVE
-             \u0625: ARABIC LETTER ALEF WITH HAMZA BELOW
-             \u0622: ARABIC LETTER ALEF WITH MADDA ABOVE -->
-        <Key
-            latin:keyLabel="ا"
-            latin:moreKeys="\u0621,\u0671,\u0623,\u0625,\u0622" />
-        <!-- \u062a: ARABIC LETTER TEH
-             \u062b: ARABIC LETTER THEH -->
-        <Key
-            latin:keyLabel="ت"
-            latin:moreKeys="ث" />
-        <!-- \u0646: ARABIC LETTER NOON -->
-        <Key
-            latin:keyLabel="ن" />
-        <!-- \u0645: ARABIC LETTER MEEM -->
-        <Key
-            latin:keyLabel="م" />
-        <!-- \u0643: ARABIC LETTER KAF
-             \u06af: ARABIC LETTER GAF
-             \u06a9: ARABIC LETTER KEHEH -->
-        <Key
-            latin:keyLabel="ك"
-            latin:moreKeys="\u06af,\u06a9"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <!-- \u0638: ARABIC LETTER ZAH -->
-        <Key
-            latin:keyLabel="ظ"
-            latin:keyXPos="5.0%p" />
-        <!-- \u0637: ARABIC LETTER TAH -->
-        <Key
-            latin:keyLabel="ط" />
-        <!-- \u0630: ARABIC LETTER THAL -->
-        <Key
-            latin:keyLabel="ذ" />
-        <!-- \u062f: ARABIC LETTER DAL -->
-        <Key
-            latin:keyLabel="د" />
-        <!-- \u0632: ARABIC LETTER ZAIN
-             \u0698: ARABIC LETTER JEH -->
-        <Key
-            latin:keyLabel="ز"
-            latin:moreKeys="ژ" />
-        <!-- \u0631: ARABIC LETTER REH -->
-        <Key
-            latin:keyLabel="ر" />
-        <!-- \u0629: ARABIC LETTER TEH MARBUTA -->
-        <Key
-            latin:keyLabel="ة" />
-        <!-- \u0648: ARABIC LETTER WAW
-             \u0624: ARABIC LETTER WAW WITH HAMZA ABOVE -->
-        <Key
-            latin:keyLabel="و"
-            latin:moreKeys="ؤ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_azerty.xml b/java/res/xml/kbd_rows_azerty.xml
deleted file mode 100644
index 54fe546..0000000
--- a/java/res/xml/kbd_rows_azerty.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:keyHintLabel="1"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="z"
-            latin:keyHintLabel="2"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="e"
-            latin:keyHintLabel="3"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:keyHintLabel="4"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:keyHintLabel="6"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:keyHintLabel="7"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:keyHintLabel="8"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:keyHintLabel="9"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:keyHintLabel="0"
-            latin:moreKeys="@string/more_keys_for_p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="m"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="15%p"
-            latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="w"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="\'"
-            latin:moreKeys="‘,’,‚,‛" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_hebrew.xml b/java/res/xml/kbd_rows_hebrew.xml
deleted file mode 100644
index 6be8174..0000000
--- a/java/res/xml/kbd_rows_hebrew.xml
+++ /dev/null
@@ -1,109 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="ק"
-            latin:keyXPos="5%p" />
-        <Key
-            latin:keyLabel="ר" />
-        <Key
-            latin:keyLabel="א" />
-        <Key
-            latin:keyLabel="ט" />
-        <Key
-            latin:keyLabel="ו" />
-        <Key
-            latin:keyLabel="ן" />
-        <Key
-            latin:keyLabel="ם" />
-        <Key
-            latin:keyLabel="פ" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="ש" />
-        <Key
-            latin:keyLabel="ד" />
-        <Key
-            latin:keyLabel="ג"
-            latin:moreKeys="ג׳" />
-        <Key
-            latin:keyLabel="כ" />
-        <Key
-            latin:keyLabel="ע" />
-        <Key
-            latin:keyLabel="י"
-            latin:moreKeys="ײַ" />
-        <Key
-            latin:keyLabel="ח"
-            latin:moreKeys="ח׳" />
-        <Key
-            latin:keyLabel="ל" />
-        <Key
-            latin:keyLabel="ך" />
-        <Key
-            latin:keyLabel="ף"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="ז"
-            latin:moreKeys="ז׳"
-            latin:keyXPos="5%p" />
-        <Key
-            latin:keyLabel="ס" />
-        <Key
-            latin:keyLabel="ב" />
-        <Key
-            latin:keyLabel="ה" />
-        <Key
-            latin:keyLabel="נ" />
-        <Key
-            latin:keyLabel="מ" />
-        <Key
-            latin:keyLabel="צ"
-            latin:moreKeys="צ׳" />
-        <Key
-            latin:keyLabel="ת"
-            latin:moreKeys="ת׳" />
-        <Key
-            latin:keyLabel="ץ"
-            latin:moreKeys="ץ׳" />
-        <!-- Here is 5%p space -->
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_number.xml b/java/res/xml/kbd_rows_number.xml
deleted file mode 100644
index 90ac568..0000000
--- a/java/res/xml/kbd_rows_number.xml
+++ /dev/null
@@ -1,132 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
-    <switch>
-        <case
-            latin:passwordInput="true"
-        >
-            <Row>
-                <Key
-                    latin:keyStyle="num1KeyStyle" />
-                <Key
-                    latin:keyStyle="num2KeyStyle" />
-                <Key
-                    latin:keyStyle="num3KeyStyle" />
-                <Spacer />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyStyle="num4KeyStyle" />
-                <Key
-                    latin:keyStyle="num5KeyStyle" />
-                <Key
-                    latin:keyStyle="num6KeyStyle" />
-                <Spacer />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyStyle="num7KeyStyle" />
-                <Key
-                    latin:keyStyle="num8KeyStyle" />
-                <Key
-                    latin:keyStyle="num9KeyStyle" />
-                <Key
-                    latin:keyStyle="deleteKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <Spacer />
-                <Key
-                    latin:keyStyle="num0KeyStyle" />
-                <Spacer />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </Row>
-        </case>
-        <!-- latin:passwordInput="false" -->
-        <default>
-            <Row>
-                <Key
-                    latin:keyLabel="1"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="2"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="3"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="-"
-                    latin:keyStyle="numFunctionalKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyLabel="4"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="5"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="6"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel=","
-                    latin:keyStyle="numFunctionalKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyLabel="7"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="8"
-                    latin:keyStyle="numKeyStyle"/>
-                <Key
-                    latin:keyLabel="9"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyStyle="deleteKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </Row>
-            <Row>
-                <Key
-                    latin:keyStyle="numSpaceKeyStyle" />
-                <Key
-                    latin:keyLabel="0"
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyLabel="."
-                    latin:keyStyle="numKeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </Row>
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml/kbd_rows_qwerty.xml b/java/res/xml/kbd_rows_qwerty.xml
deleted file mode 100644
index 6237712..0000000
--- a/java/res/xml/kbd_rows_qwerty.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_qwertz.xml b/java/res/xml/kbd_rows_qwertz.xml
deleted file mode 100644
index 71bb601..0000000
--- a/java/res/xml/kbd_rows_qwertz.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:keyHintLabel="1"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:keyHintLabel="2"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:keyHintLabel="3"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:keyHintLabel="4"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="z"
-            latin:keyHintLabel="6"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="u"
-            latin:keyHintLabel="7"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:keyHintLabel="8"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:keyHintLabel="9"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:keyHintLabel="0"
-            latin:moreKeys="@string/more_keys_for_p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="15%p"
-            latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="y"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-   <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_russian.xml b/java/res/xml/kbd_rows_russian.xml
deleted file mode 100644
index f1794e7..0000000
--- a/java/res/xml/kbd_rows_russian.xml
+++ /dev/null
@@ -1,133 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="9.091%p"
-    >
-        <Key
-            latin:keyLabel="й"
-            latin:keyHintLabel="1"
-            latin:moreKeys="1" />
-        <Key
-            latin:keyLabel="ц"
-            latin:keyHintLabel="2"
-            latin:moreKeys="2" />
-        <Key
-            latin:keyLabel="у"
-            latin:keyHintLabel="3"
-            latin:moreKeys="3" />
-        <Key
-            latin:keyLabel="к"
-            latin:keyHintLabel="4"
-            latin:moreKeys="4" />
-        <Key
-            latin:keyLabel="е"
-            latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
-        <Key
-            latin:keyLabel="н"
-            latin:keyHintLabel="6"
-            latin:moreKeys="6" />
-        <Key
-            latin:keyLabel="г"
-            latin:keyHintLabel="7"
-            latin:moreKeys="7" />
-        <Key
-            latin:keyLabel="ш"
-            latin:keyHintLabel="8"
-            latin:moreKeys="8" />
-        <Key
-            latin:keyLabel="щ"
-            latin:keyHintLabel="9"
-            latin:moreKeys="9" />
-        <Key
-            latin:keyLabel="з"
-            latin:keyHintLabel="0"
-            latin:moreKeys="0" />
-        <Key
-            latin:keyLabel="х"
-            latin:moreKeys="@string/more_keys_for_cyrillic_ha"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-            latin:keyWidth="9.091%p"
-    >
-        <Key
-            latin:keyLabel="ф"
-            latin:keyWidth="8.75%p" />
-        <Key
-            latin:keyLabel="ы" />
-        <Key
-            latin:keyLabel="в" />
-        <Key
-            latin:keyLabel="а" />
-        <Key
-            latin:keyLabel="п" />
-        <Key
-            latin:keyLabel="р" />
-        <Key
-            latin:keyLabel="о" />
-        <Key
-            latin:keyLabel="л" />
-        <Key
-            latin:keyLabel="д" />
-        <Key
-            latin:keyLabel="ж" />
-        <Key
-            latin:keyLabel="э"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.5%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="11.75%p" />
-        <Key
-            latin:keyLabel="я" />
-        <Key
-            latin:keyLabel="ч" />
-        <Key
-            latin:keyLabel="с" />
-        <Key
-            latin:keyLabel="м" />
-        <Key
-            latin:keyLabel="и" />
-        <Key
-            latin:keyLabel="т" />
-        <Key
-            latin:keyLabel="ь"
-            latin:moreKeys="@string/more_keys_for_cyrillic_soft_sign" />
-        <Key
-            latin:keyLabel="б" />
-        <Key
-            latin:keyLabel="ю" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_scandinavian.xml b/java/res/xml/kbd_rows_scandinavian.xml
deleted file mode 100644
index 4f138c5..0000000
--- a/java/res/xml/kbd_rows_scandinavian.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="9.091%p"
-    >
-        <Key
-            latin:keyLabel="q"
-            latin:keyHintLabel="1"
-            latin:moreKeys="@string/more_keys_for_q" />
-        <Key
-            latin:keyLabel="w"
-            latin:keyHintLabel="2"
-            latin:moreKeys="@string/more_keys_for_w" />
-        <Key
-            latin:keyLabel="e"
-            latin:keyHintLabel="3"
-            latin:moreKeys="@string/more_keys_for_e" />
-        <Key
-            latin:keyLabel="r"
-            latin:keyHintLabel="4"
-            latin:moreKeys="@string/more_keys_for_r" />
-        <Key
-            latin:keyLabel="t"
-            latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_t" />
-        <Key
-            latin:keyLabel="y"
-            latin:keyHintLabel="6"
-            latin:moreKeys="@string/more_keys_for_y" />
-        <Key
-            latin:keyLabel="u"
-            latin:keyHintLabel="7"
-            latin:moreKeys="@string/more_keys_for_u" />
-        <Key
-            latin:keyLabel="i"
-            latin:keyHintLabel="8"
-            latin:moreKeys="@string/more_keys_for_i" />
-        <Key
-            latin:keyLabel="o"
-            latin:keyHintLabel="9"
-            latin:moreKeys="@string/more_keys_for_o" />
-        <Key
-            latin:keyLabel="p"
-            latin:keyHintLabel="0"
-            latin:moreKeys="@string/more_keys_for_p" />
-        <Key
-            latin:keyLabel="å"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="9.091%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a"
-            latin:keyWidth="8.75%p" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_scandinavia_row2_10"
-            latin:moreKeys="@string/more_keys_for_scandinavia_row2_10" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_scandinavia_row2_11"
-            latin:moreKeys="@string/more_keys_for_scandinavia_row2_11"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_serbian.xml b/java/res/xml/kbd_rows_serbian.xml
deleted file mode 100644
index da4d695..0000000
--- a/java/res/xml/kbd_rows_serbian.xml
+++ /dev/null
@@ -1,130 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:keyWidth="9.091%p"
-    >
-        <Key
-            latin:keyLabel="љ"
-            latin:keyHintLabel="1"
-            latin:moreKeys="1" />
-        <Key
-            latin:keyLabel="њ"
-            latin:keyHintLabel="2"
-            latin:moreKeys="2" />
-        <Key
-            latin:keyLabel="е"
-            latin:keyHintLabel="3"
-            latin:moreKeys="3" />
-        <Key
-            latin:keyLabel="р"
-            latin:keyHintLabel="4"
-            latin:moreKeys="4" />
-        <Key
-            latin:keyLabel="т"
-            latin:keyHintLabel="5"
-            latin:moreKeys="5" />
-        <Key
-            latin:keyLabel="з"
-            latin:keyHintLabel="6"
-            latin:moreKeys="6" />
-        <Key
-            latin:keyLabel="у"
-            latin:keyHintLabel="7"
-            latin:moreKeys="7" />
-        <Key
-            latin:keyLabel="и"
-            latin:keyHintLabel="8"
-            latin:moreKeys="8" />
-        <Key
-            latin:keyLabel="о"
-            latin:keyHintLabel="9"
-            latin:moreKeys="9" />
-        <Key
-            latin:keyLabel="п"
-            latin:keyHintLabel="0"
-            latin:moreKeys="0" />
-        <Key
-            latin:keyLabel="ш"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="9.091%p"
-    >
-        <Key
-            latin:keyLabel="а" />
-        <Key
-            latin:keyLabel="с" />
-        <Key
-            latin:keyLabel="д" />
-        <Key
-            latin:keyLabel="ф" />
-        <Key
-            latin:keyLabel="г" />
-        <Key
-            latin:keyLabel="х" />
-        <Key
-            latin:keyLabel="ј" />
-        <Key
-            latin:keyLabel="к" />
-        <Key
-            latin:keyLabel="л" />
-        <Key
-            latin:keyLabel="ч" />
-        <Key
-            latin:keyLabel="ћ"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.5%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="11.75%p" />
-        <Key
-            latin:keyLabel="ѕ" />
-        <Key
-            latin:keyLabel="џ" />
-        <Key
-            latin:keyLabel="ц" />
-        <Key
-            latin:keyLabel="в" />
-        <Key
-            latin:keyLabel="б" />
-        <Key
-            latin:keyLabel="н" />
-        <Key
-            latin:keyLabel="м" />
-        <Key
-            latin:keyLabel="ђ" />
-        <Key
-            latin:keyLabel="ж" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_spanish.xml b/java/res/xml/kbd_rows_spanish.xml
deleted file mode 100644
index 03d631e..0000000
--- a/java/res/xml/kbd_rows_spanish.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a" />
-        <Key
-            latin:keyLabel="s"
-            latin:moreKeys="@string/more_keys_for_s" />
-        <Key
-            latin:keyLabel="d"
-            latin:moreKeys="@string/more_keys_for_d" />
-        <Key
-            latin:keyLabel="f" />
-        <Key
-            latin:keyLabel="g"
-            latin:moreKeys="@string/more_keys_for_g" />
-        <Key
-            latin:keyLabel="h" />
-        <Key
-            latin:keyLabel="j" />
-        <Key
-            latin:keyLabel="k"
-            latin:moreKeys="@string/more_keys_for_k" />
-        <Key
-            latin:keyLabel="l"
-            latin:moreKeys="@string/more_keys_for_l" />
-        <Key
-            latin:keyLabel="ñ" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_symbols.xml b/java/res/xml/kbd_rows_symbols.xml
deleted file mode 100644
index c5bcb14..0000000
--- a/java/res/xml/kbd_rows_symbols.xml
+++ /dev/null
@@ -1,130 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_currency_key_styles" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_1"
-            latin:moreKeys="@string/more_keys_for_symbols_1" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_2"
-            latin:moreKeys="@string/more_keys_for_symbols_2" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_3"
-            latin:moreKeys="@string/more_keys_for_symbols_3" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_4"
-            latin:moreKeys="@string/more_keys_for_symbols_4" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_5"
-            latin:moreKeys="@string/more_keys_for_symbols_5" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_6"
-            latin:moreKeys="@string/more_keys_for_symbols_6" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_7"
-            latin:moreKeys="@string/more_keys_for_symbols_7" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_8"
-            latin:moreKeys="@string/more_keys_for_symbols_8" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_9"
-            latin:moreKeys="@string/more_keys_for_symbols_9" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_0"
-            latin:moreKeys="@string/more_keys_for_symbols_0"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="\@" />
-        <Key
-            latin:keyLabel="\#" />
-        <Key
-            latin:keyStyle="currencyKeyStyle" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_percent"
-            latin:moreKeys="@string/more_keys_for_symbols_percent" />
-        <Key
-            latin:keyLabel="&amp;" />
-        <Key
-            latin:keyLabel="*"
-            latin:moreKeys="@string/more_keys_for_star" />
-        <Key
-            latin:keyLabel="-"
-            latin:moreKeys="_,–,—" />
-        <Key
-            latin:keyLabel="+"
-            latin:moreKeys="@string/more_keys_for_plus" />
-        <Key
-            latin:keyLabel="("
-            latin:moreKeys="@string/more_keys_for_left_parenthesis" />
-        <Key
-            latin:keyLabel=")"
-            latin:moreKeys="@string/more_keys_for_right_parenthesis"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="15%p"
-            latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="!"
-            latin:moreKeys="¡" />
-        <!-- Note: Neither DroidSans nor Roboto have a glyph for ‟ Double high-reversed-9 quotation mark U+201F. -->
-            <!-- latin:moreKeys="“,”,„,‟,«,»" -->
-        <Key
-            latin:keyLabel="&quot;"
-            latin:moreKeys="“,”,«,»"
-            latin:maxMoreKeysColumn="6" />
-        <Key
-            latin:keyLabel="\'"
-            latin:moreKeys="‘,’,‚,‛" />
-        <Key
-            latin:keyLabel=":" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_semicolon"
-            latin:moreKeys="@string/more_keys_for_symbols_semicolon" />
-        <Key
-            latin:keyLabel="/" />
-        <Key
-            latin:keyLabel="@string/keylabel_for_symbols_question"
-            latin:moreKeys="@string/more_keys_for_symbols_question" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_symbols_row4" />
-</merge>
diff --git a/java/res/xml/kbd_rows_symbols_shift.xml b/java/res/xml/kbd_rows_symbols_shift.xml
deleted file mode 100644
index 91654b0..0000000
--- a/java/res/xml/kbd_rows_symbols_shift.xml
+++ /dev/null
@@ -1,114 +0,0 @@
-<?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.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_currency_key_styles" />
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyLabel="~" />
-        <Key
-            latin:keyLabel="`" />
-        <Key
-            latin:keyLabel="|" />
-        <Key
-            latin:keyLabel="•"
-            latin:moreKeys="@string/more_keys_for_bullet" />
-        <Key
-            latin:keyLabel="√" />
-        <Key
-            latin:keyLabel="π"
-            latin:moreKeys="Π" />
-        <Key
-            latin:keyLabel="÷" />
-        <Key
-            latin:keyLabel="×" />
-        <Key
-            latin:keyLabel="{" />
-        <Key
-            latin:keyLabel="}"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="nonSpecialBackgroundTabKeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency1KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency2KeyStyle" />
-        <Key
-            latin:keyStyle="moreCurrency3KeyStyle" />
-        <Key
-            latin:keyLabel="°"
-            latin:moreKeys="′,″" />
-        <Key
-            latin:keyLabel="^"
-            latin:moreKeys="↑,↓,←,→" />
-        <Key
-            latin:keyLabel="_" />
-        <Key
-            latin:keyLabel="="
-            latin:moreKeys="≠,≈,∞" />
-        <Key
-            latin:keyLabel="[" />
-        <Key
-            latin:keyLabel="]"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="15%p"
-            latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="™" />
-        <Key
-            latin:keyLabel="®" />
-        <Key
-            latin:keyLabel="©" />
-        <Key
-            latin:keyLabel="¶"
-            latin:moreKeys="§" />
-        <Key
-            latin:keyLabel="\\" />
-        <Key
-            latin:keyLabel="&lt;"
-            latin:moreKeys="≤,«,‹" />
-        <Key
-            latin:keyLabel="&gt;"
-            latin:moreKeys="≥,»,›" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/kbd_symbols_shift_row4" />
-</merge>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_south_slavic.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_south_slavic.xml
index 3195d5b..f3ad02c 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_south_slavic.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_south_slavic" />
 </Keyboard>
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_spanish.xml
similarity index 89%
copy from java/res/xml-hu/kbd_qwerty.xml
copy to java/res/xml/kbd_spanish.xml
index 3195d5b..6ce2b5d 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_spanish.xml
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
+        latin:keyboardLayout="@xml/rows_spanish" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_spanish_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_spanish_symbols.xml
index e29d9ab..7e075df 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_spanish_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_spanish_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_spanish_symbols_shift.xml
index e29d9ab..25db3c8 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_spanish_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml/kbd_symbols.xml b/java/res/xml/kbd_symbols.xml
index 737f684..f6612a2 100644
--- a/java/res/xml/kbd_symbols.xml
+++ b/java/res/xml/kbd_symbols.xml
@@ -22,5 +22,5 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml/kbd_symbols_f1.xml b/java/res/xml/kbd_symbols_f1.xml
deleted file mode 100644
index 0dd3d91..0000000
--- a/java/res/xml/kbd_symbols_f1.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:hasSettingsKey="false"
-            latin:navigateAction="false"
-        >
-            <switch>
-                <case
-                    latin:hasShortcutKey="true"
-                >
-                    <Key
-                        latin:keyStyle="shortcutKeyStyle" />
-                </case>
-                <!-- latin:hasShortcutKey="false" -->
-                <default>
-                    <Key
-                        latin:keyLabel="@string/keylabel_for_f1"
-                        latin:keyStyle="f1PopupStyle" />
-                </default>
-            </switch>
-        </case>
-        <!-- hasSettingsKey="true" or navigateAction="true" -->
-        <default>
-            <switch>
-                <case
-                    latin:hasShortcutKey="true"
-                >
-                    <Key
-                        latin:keyStyle="shortcutKeyStyle"
-                        latin:keyWidth="9.2%p" />
-                </case>
-                <!-- latin:hasShortcutKey="false" -->
-                <default>
-                    <Key
-                        latin:keyLabel="@string/keylabel_for_f1"
-                        latin:keyWidth="9.2%p"
-                        latin:keyStyle="f1PopupStyle" />
-                </default>
-            </switch>
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml/kbd_symbols_row4.xml b/java/res/xml/kbd_symbols_row4.xml
deleted file mode 100644
index 864cf2b..0000000
--- a/java/res/xml/kbd_symbols_row4.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <switch>
-            <case
-                latin:hasSettingsKey="false"
-                latin:navigateAction="false"
-            >
-                <Key
-                    latin:keyStyle="toAlphaKeyStyle"
-                    latin:keyWidth="15%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_symbols_f1" />
-                <Key
-                    latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="50%p" />
-                <Key
-                    latin:keyStyle="punctuationKeyStyle" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </case>
-            <!-- hasSettingsKey="true" or navigateAction="true" -->
-            <default>
-                <Key
-                    latin:keyStyle="toAlphaKeyStyle"
-                    latin:keyWidth="13.75%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_settings_or_tab" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_f1" />
-                <Key
-                    latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="35.83%p" />
-                <Key
-                    latin:keyStyle="punctuationKeyStyle"
-                    latin:keyWidth="9.2%p" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </default>
-        </switch>
-    </Row>
-</merge>
diff --git a/java/res/xml/kbd_symbols_shift.xml b/java/res/xml/kbd_symbols_shift.xml
index 9c163d6..41a5571 100644
--- a/java/res/xml/kbd_symbols_shift.xml
+++ b/java/res/xml/kbd_symbols_shift.xml
@@ -22,5 +22,5 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols_shift" />
+        latin:keyboardLayout="@xml/rows_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml/kbd_symbols_shift_row4.xml b/java/res/xml/kbd_symbols_shift_row4.xml
deleted file mode 100644
index 89e80e5..0000000
--- a/java/res/xml/kbd_symbols_shift_row4.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <switch>
-            <case
-                latin:hasSettingsKey="false"
-                latin:navigateAction="false"
-            >
-                <Key
-                    latin:keyStyle="toAlphaKeyStyle"
-                    latin:keyWidth="15%p" />
-                <!-- Note: Neither DroidSans nor Roboto have a glyph for ‟ Double high-reversed-9 quotation mark U+201F. -->
-                    <!-- latin:keyLabelOption="hasPopupHint" -->
-                    <!-- latin:moreKeys="‟" -->
-                <Key
-                    latin:keyLabel="„"
-                    latin:backgroundType="functional" />
-                <Key
-                    latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="50%p" />
-                <Key
-                    latin:keyLabel="…"
-                    latin:backgroundType="functional" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </case>
-            <!-- hasSettingsKey="true" or navigateAction="true" -->
-            <default>
-                <Key
-                    latin:keyStyle="toAlphaKeyStyle"
-                    latin:keyWidth="13.75%p" />
-                <include
-                    latin:keyboardLayout="@xml/kbd_settings_or_tab" />
-                <!-- Note: Neither DroidSans nor Roboto have a glyph for ‟ Double high-reversed-9 quotation mark U+201F. -->
-                    <!-- latin:keyLabelOption="hasPopupHint" -->
-                    <!-- latin:moreKeys="‟" -->
-                <Key
-                    latin:keyLabel="„"
-                    latin:keyWidth="9.2%p"
-                    latin:backgroundType="functional" />
-                <Key
-                    latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="35.83%p" />
-                <Key
-                    latin:keyLabel="…"
-                    latin:keyWidth="9.2%p"
-                    latin:backgroundType="functional" />
-                <Key
-                    latin:keyStyle="returnKeyStyle"
-                    latin:keyWidth="fillRight" />
-            </default>
-        </switch>
-    </Row>
-</merge>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_thai.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_thai.xml
index e29d9ab..058ca16 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_thai.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_thai" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_thai_symbols.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_thai_symbols.xml
index e29d9ab..7e075df 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_thai_symbols.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/kbd_thai_symbols_shift.xml
similarity index 83%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/kbd_thai_symbols_shift.xml
index e29d9ab..25db3c8 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/kbd_thai_symbols_shift.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
+        latin:keyboardLayout="@xml/rows_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml/kbd_settings_or_tab.xml b/java/res/xml/key_azerty_quote.xml
similarity index 63%
copy from java/res/xml/kbd_settings_or_tab.xml
copy to java/res/xml/key_azerty_quote.xml
index 4a8bcc7..8c44f78 100644
--- a/java/res/xml/kbd_settings_or_tab.xml
+++ b/java/res/xml/key_azerty_quote.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -23,23 +23,15 @@
 >
     <switch>
         <case
-            latin:hasSettingsKey="true"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
         >
             <Key
-                latin:keyStyle="settingsKeyStyle"
-                latin:keyWidth="9.2%p" />
+                latin:keyLabel="\?" />
         </case>
-        <!-- hasSettingsKey="false" -->
-        <case
-            latin:navigateAction="true"
-        >
-            <Key
-                latin:keyStyle="tabKeyStyle"
-                latin:keyWidth="9.2%p" />
-        </case>
-        <!-- hasSettingsKey="false" and navigateAction="false" -->
         <default>
-            <!-- No key. -->
+            <Key
+                latin:keyLabel="\'"
+                latin:moreKeys="\@string/more_keys_for_single_quote" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/kbd_settings_or_tab.xml b/java/res/xml/key_greek_semicolon.xml
similarity index 63%
rename from java/res/xml/kbd_settings_or_tab.xml
rename to java/res/xml/key_greek_semicolon.xml
index 4a8bcc7..a28b772 100644
--- a/java/res/xml/kbd_settings_or_tab.xml
+++ b/java/res/xml/key_greek_semicolon.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -23,23 +23,20 @@
 >
     <switch>
         <case
-            latin:hasSettingsKey="true"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
         >
             <Key
-                latin:keyStyle="settingsKeyStyle"
-                latin:keyWidth="9.2%p" />
+                latin:keyLabel=":"
+                latin:keyHintLabel="1"
+                latin:moreKeys=";"
+                latin:additionalMoreKeys="1" />
         </case>
-        <!-- hasSettingsKey="false" -->
-        <case
-            latin:navigateAction="true"
-        >
-            <Key
-                latin:keyStyle="tabKeyStyle"
-                latin:keyWidth="9.2%p" />
-        </case>
-        <!-- hasSettingsKey="false" and navigateAction="false" -->
         <default>
-            <!-- No key. -->
+            <Key
+                latin:keyLabel=";"
+                latin:keyHintLabel="1"
+                latin:moreKeys=":"
+                latin:additionalMoreKeys="1" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
new file mode 100644
index 0000000..d5a9a26
--- /dev/null
+++ b/java/res/xml/key_styles_common.xml
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- Base key style for the key which may have settings or tab key as popup key. -->
+    <switch>
+        <case
+            latin:clobberSettingsKey="true"
+        >
+            <key-style
+                latin:styleName="f1MoreKeysStyle"
+                latin:backgroundType="functional" />
+        </case>
+        <!-- clobberSettingsKey="false" -->
+        <default>
+            <key-style
+                latin:styleName="f1MoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="@string/settings_as_more_key"
+                latin:backgroundType="functional" />
+        </default>
+    </switch>
+    <!-- Functional key styles -->
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetAutomaticShifted"
+        >
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKeyShifted"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOff" />
+        </case>
+        <case
+            latin:keyboardSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKeyShifted"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOn" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="shiftKeyStyle"
+                latin:code="@integer/key_shift"
+                latin:keyIcon="iconShiftKey"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="stickyOff" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="deleteKeyStyle"
+        latin:code="@integer/key_delete"
+        latin:keyIcon="iconDeleteKey"
+        latin:keyActionFlags="isRepeatable|noKeyPreview"
+        latin:backgroundType="functional" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_enter" />
+    <switch>
+        <!-- Shift + Enter in textMultiLine field. -->
+        <case
+            latin:isMultiLine="true"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <!-- Smiley in textShortMessage field.
+             Overrides common enter key style. -->
+        <case
+            latin:mode="im"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabel=":-)"
+                latin:keyOutputText=":-) "
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="@string/more_keys_for_smiley"
+                latin:backgroundType="functional" />
+        </case>
+    </switch>
+    <key-style
+        latin:styleName="spaceKeyStyle"
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview|enableLongPress"
+        latin:backgroundType="functional" />
+    <!-- U+200C: ZERO WIDTH NON-JOINER
+         U+200D: ZERO WIDTH JOINER -->
+    <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:code="0x200C"
+        latin:keyIcon="iconZwnjKey"
+        latin:moreKeys="\@icon/zwjKey|&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="shortcutKeyStyle"
+        latin:code="@integer/key_shortcut"
+        latin:keyIcon="iconShortcutKey"
+        latin:keyIconDisabled="iconDisabledShortcutKey"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
+        latin:parentStyle="f1MoreKeysStyle" />
+    <key-style
+        latin:styleName="languageSwitchKeyStyle"
+        latin:code="@integer/key_language_switch"
+        latin:keyIcon="iconLanguageSwitchKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping|enableLongPress"
+        latin:altCode="@integer/key_space"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="tabKeyStyle"
+        latin:code="@integer/key_tab"
+        latin:keyIcon="iconTabKey"
+        latin:keyIconPreview="iconPreviewTabKey"
+        latin:backgroundType="functional" />
+    <!-- Note: This key style is not for functional tab key. This is used for the tab key which is
+         laid out as normal letter key. -->
+    <key-style
+        latin:styleName="nonSpecialBackgroundTabKeyStyle"
+        latin:code="@integer/key_tab"
+        latin:keyIcon="iconTabKey"
+        latin:keyIconPreview="iconPreviewTabKey" />
+    <switch>
+        <!-- When this qwerty keyboard has no shortcut keys but shortcut key is enabled, then symbol
+             keyboard will have a shortcut key. That means we should use
+             iconToSymbolKeyWithShortcutKey here. -->
+        <case
+            latin:shortcutKeyEnabled="true"
+            latin:hasShortcutKey="false"
+        >
+            <key-style
+                latin:styleName="toSymbolKeyStyle"
+                latin:code="@integer/key_switch_alpha_symbol"
+                latin:keyIcon="iconShortcutForLabel"
+                latin:keyLabel="@string/label_to_symbol_with_microphone_key"
+                latin:keyLabelFlags="withIconRight|preserveCase"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="functional" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="toSymbolKeyStyle"
+                latin:code="@integer/key_switch_alpha_symbol"
+                latin:keyLabel="@string/label_to_symbol_key"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="functional" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="toAlphaKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
+        latin:keyLabel="@string/label_to_alpha_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="toMoreSymbolKeyStyle"
+        latin:code="@integer/key_shift"
+        latin:keyLabel="@string/label_to_more_symbol_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="backFromMoreSymbolKeyStyle"
+        latin:code="@integer/key_shift"
+        latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyLabelFlags="preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="punctuationKeyStyle"
+        latin:keyLabel="."
+        latin:keyHintLabel="@string/keyhintlabel_for_punctuation"
+        latin:keyLabelFlags="hasPopupHint|preserveCase"
+        latin:moreKeys="@string/more_keys_for_punctuation"
+        latin:backgroundType="functional" />
+</merge>
diff --git a/java/res/xml/kbd_currency_key_styles.xml b/java/res/xml/key_styles_currency.xml
similarity index 66%
rename from java/res/xml/kbd_currency_key_styles.xml
rename to java/res/xml/key_styles_currency.xml
index 2258883..bd1d959 100644
--- a/java/res/xml/kbd_currency_key_styles.xml
+++ b/java/res/xml/key_styles_currency.xml
@@ -26,7 +26,7 @@
             latin:passwordInput="true"
         >
             <include
-                latin:keyboardLayout="@xml/kbd_currency_dollar_key_styles" />
+                latin:keyboardLayout="@xml/key_styles_currency_dollar" />
         </case>
         <!-- Countries using Euro currency, 23 countries as for January 2011. -->
               1. Andorra (ca_AD, ca_ES)
@@ -62,26 +62,56 @@
             latin:localeCode="da|de|es|el|fi|fr|it|nl|sk|sl|pt_PT|tr"
         >
             <include
-                latin:keyboardLayout="@xml/kbd_currency_euro_key_styles" />
+                latin:keyboardLayout="@xml/key_styles_currency_euro" />
         </case>
         <case
             latin:languageCode="ca|et|lb|mt|sla"
         >
             <include
-                latin:keyboardLayout="@xml/kbd_currency_euro_key_styles" />
+                latin:keyboardLayout="@xml/key_styles_currency_euro" />
         </case>
         <case
             latin:countryCode="AD|AT|BE|CY|EE|FI|FR|DE|GR|IE|IT|XK|LU|MT|MO|ME|NL|PT|SM|SK|SI|ES|VA"
         >
             <include
-                latin:keyboardLayout="@xml/kbd_currency_euro_key_styles" />
+                latin:keyboardLayout="@xml/key_styles_currency_euro" />
         </case>
         <case
             latin:languageCode="iw"
         >
+            <!-- U+20AA: "₪" NEW SHEQEL SIGN
+                 U+00A3: "£" POUND SIGN
+                 U+20AC: "€" EURO SIGN
+                 U+00A2: "¢" CENT SIGN -->
             <key-style
                 latin:styleName="currencyKeyStyle"
-                latin:keyLabel="₪"
+                latin:keyLabel="&#x20AA;"
+                latin:moreKeys="@string/more_keys_for_currency_general" />
+            <key-style
+                latin:styleName="moreCurrency1KeyStyle"
+                latin:keyLabel="&#x00A3;" />
+            <key-style
+                latin:styleName="moreCurrency2KeyStyle"
+                latin:keyLabel="&#x20AC;" />
+            <key-style
+                latin:styleName="moreCurrency3KeyStyle"
+                latin:keyLabel="$"
+                latin:moreKeys="&#x00A2;" />
+            <key-style
+                latin:styleName="moreCurrency4KeyStyle"
+                latin:keyLabel="&#x00A2;" />
+        </case>
+        <case
+            latin:languageCode="fa"
+        >
+            <!-- U+FDFC: "﷼" RIAL SIGN
+                 U+00A3: "£" POUND SIGN
+                 U+20AC: "€" EURO SIGN
+                 U+00A2: "¢" CENT SIGN -->
+            <!-- TODO: DroidSansArabic lacks the glyph of U+FCDC: RIAL SIGN -->
+            <key-style
+                latin:styleName="currencyKeyStyle"
+                latin:keyLabel="&#xFDFC;"
                 latin:moreKeys="@string/more_keys_for_currency_general" />
             <key-style
                 latin:styleName="moreCurrency1KeyStyle"
@@ -101,27 +131,31 @@
         <case
             latin:countryCode="GB"
         >
+            <!-- U+00A3: "£" POUND SIGN
+                 U+20AC: "€" EURO SIGN
+                 U+00A5: "¥" YEN SIGN
+                 U+00A2: "¢" CENT SIGN -->
             <key-style
                 latin:styleName="currencyKeyStyle"
-                latin:keyLabel="£"
+                latin:keyLabel="&#x00A3;"
                 latin:moreKeys="@string/more_keys_for_currency_pound" />
             <key-style
                 latin:styleName="moreCurrency1KeyStyle"
-                latin:keyLabel="€" />
+                latin:keyLabel="&#x20AC;" />
             <key-style
                 latin:styleName="moreCurrency2KeyStyle"
-                latin:keyLabel="¥" />
+                latin:keyLabel="&#x00A5;" />
             <key-style
                 latin:styleName="moreCurrency3KeyStyle"
                 latin:keyLabel="$"
-                latin:moreKeys="¢" />
+                latin:moreKeys="&#x00A2;" />
             <key-style
                 latin:styleName="moreCurrency4KeyStyle"
-                latin:keyLabel="¢" />
+                latin:keyLabel="&#x00A2;" />
         </case>
         <default>
             <include
-                latin:keyboardLayout="@xml/kbd_currency_dollar_key_styles" />
+                latin:keyboardLayout="@xml/key_styles_currency_dollar" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/kbd_currency_dollar_key_styles.xml b/java/res/xml/key_styles_currency_dollar.xml
similarity index 80%
rename from java/res/xml/kbd_currency_dollar_key_styles.xml
rename to java/res/xml/key_styles_currency_dollar.xml
index d5dca2a..8dd8498 100644
--- a/java/res/xml/kbd_currency_dollar_key_styles.xml
+++ b/java/res/xml/key_styles_currency_dollar.xml
@@ -19,20 +19,24 @@
 -->
 
 <merge xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <!-- U+00A3: "£" POUND SIGN
+         U+00A2: "¢" CENT SIGN
+         U+20AC: "€" EURO SIGN
+         U+00A5: "¥" YEN SIGN -->
     <key-style
         latin:styleName="currencyKeyStyle"
         latin:keyLabel="$"
         latin:moreKeys="@string/more_keys_for_currency_dollar" />
     <key-style
         latin:styleName="moreCurrency1KeyStyle"
-        latin:keyLabel="£" />
+        latin:keyLabel="&#x00A3;" />
     <key-style
         latin:styleName="moreCurrency2KeyStyle"
-        latin:keyLabel="¢" />
+        latin:keyLabel="&#x00A2;" />
     <key-style
         latin:styleName="moreCurrency3KeyStyle"
-        latin:keyLabel="€" />
+        latin:keyLabel="&#x20AC;" />
     <key-style
         latin:styleName="moreCurrency4KeyStyle"
-        latin:keyLabel="¥" />
+        latin:keyLabel="&#x00A5;" />
 </merge>
diff --git a/java/res/xml/kbd_currency_euro_key_styles.xml b/java/res/xml/key_styles_currency_euro.xml
similarity index 78%
rename from java/res/xml/kbd_currency_euro_key_styles.xml
rename to java/res/xml/key_styles_currency_euro.xml
index 6edddf0..0573e09 100644
--- a/java/res/xml/kbd_currency_euro_key_styles.xml
+++ b/java/res/xml/key_styles_currency_euro.xml
@@ -19,21 +19,25 @@
 -->
 
 <merge xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <!-- U+20AC: "€" EURO SIGN
+         U+00A3: "£" POUND SIGN
+         U+00A5: "¥" YEN SIGN
+         U+00A2: "¢" CENT SIGN -->
     <key-style
         latin:styleName="currencyKeyStyle"
-        latin:keyLabel="€"
+        latin:keyLabel="&#x20AC;"
         latin:moreKeys="@string/more_keys_for_currency_euro" />
     <key-style
         latin:styleName="moreCurrency1KeyStyle"
-        latin:keyLabel="£" />
+        latin:keyLabel="&#x00A3;" />
     <key-style
         latin:styleName="moreCurrency2KeyStyle"
-        latin:keyLabel="¥" />
+        latin:keyLabel="&#x00A5;" />
     <key-style
         latin:styleName="moreCurrency3KeyStyle"
         latin:keyLabel="$"
-        latin:moreKeys="¢" />
+        latin:moreKeys="&#x00A2;" />
     <key-style
         latin:styleName="moreCurrency4KeyStyle"
-        latin:keyLabel="¢" />
+        latin:keyLabel="&#x00A2;" />
 </merge>
diff --git a/java/res/xml/key_styles_enter.xml b/java/res/xml/key_styles_enter.xml
new file mode 100644
index 0000000..a4c9a33
--- /dev/null
+++ b/java/res/xml/key_styles_enter.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- Navigate more keys style -->
+    <switch>
+        <case
+            latin:imeAction="actionNext"
+            latin:navigatePrevious="true"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="@string/action_previous_as_more_key" />
+        </case>
+        <case
+            latin:imeAction="actionNext"
+            latin:navigatePrevious="false"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
+            latin:navigateNext="true"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="@string/action_next_as_more_key" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
+            latin:navigateNext="false"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle" />
+        </case>
+        <!-- imeAction!="actionNext" and imeAction!="actionPrevious" -->
+        <case
+            latin:navigateNext="true"
+            latin:navigatePrevious="true"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,@string/action_previous_as_more_key,@string/action_next_as_more_key" />
+        </case>
+        <case
+            latin:navigateNext="true"
+            latin:navigatePrevious="false"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="@string/action_next_as_more_key" />
+        </case>
+        <case
+            latin:navigateNext="false"
+            latin:navigatePrevious="true"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="@string/action_previous_as_more_key" />
+        </case>
+        <!-- naviagteNext="false" and navigatePrevious="false" -->
+        <default>
+            <key-style
+                latin:styleName="navigateMoreKeysStyle" />
+        </default>
+    </switch>
+    <!-- Enter key style -->
+    <key-style
+        latin:styleName="defaultEnterKeyStyle"
+        latin:code="@integer/key_enter"
+        latin:keyIcon="iconReturnKey"
+        latin:keyLabelFlags="autoXScale|preserveCase"
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional"
+        latin:parentStyle="navigateMoreKeysStyle" />
+    <key-style
+        latin:styleName="defaultActionKeyStyle"
+        latin:code="@integer/key_action_enter"
+        latin:keyIcon="iconUndefined"
+        latin:backgroundType="action"
+        latin:parentStyle="defaultEnterKeyStyle" />
+    <switch>
+        <!-- Shift + Enter in textMultiLine field. -->
+        <case
+            latin:isMultiLine="true"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionGo"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabel="@string/label_go_key"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionNext"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabel="@string/label_next_key"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabel="@string/label_previous_key"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionDone"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabel="@string/label_done_key"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionSend"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabel="@string/label_send_key"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionSearch"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyIcon="iconSearchKey"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionCustomLabel"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keyLabelFlags="fromCustomActionLabel"
+                latin:parentStyle="defaultActionKeyStyle" />
+        </case>
+        <!-- imeAction is either actionNone or actionUnspecified. -->
+        <default>
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/kbd_numkey_styles.xml b/java/res/xml/key_styles_number.xml
similarity index 62%
rename from java/res/xml/kbd_numkey_styles.xml
rename to java/res/xml/key_styles_number.xml
index 5d54399..1fc2169 100644
--- a/java/res/xml/kbd_numkey_styles.xml
+++ b/java/res/xml/key_styles_number.xml
@@ -22,23 +22,28 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <key-style
+        latin:styleName="numKeyBaseStyle"
+        latin:keyActionFlags="noKeyPreview" />
+    <key-style
         latin:styleName="numKeyStyle"
-        latin:keyLabelOption="largeLetter|followKeyLetterRatio" />
+        latin:keyLabelFlags="largeLetter|followKeyLetterRatio"
+        latin:parentStyle="numKeyBaseStyle" />
     <key-style
         latin:styleName="numModeKeyStyle"
-        latin:keyLabelOption="fontNormal|followKeyLetterRatio" />
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio"
+        latin:parentStyle="numKeyBaseStyle" />
     <key-style
         latin:styleName="numFunctionalKeyStyle"
-        latin:keyLabelOption="largeLetter|followKeyLetterRatio"
-        latin:backgroundType="functional" />
+        latin:keyLabelFlags="largeLetter|followKeyLetterRatio"
+        latin:backgroundType="functional"
+        latin:parentStyle="numKeyBaseStyle" />
     <key-style
         latin:styleName="numberKeyStyle"
-        latin:keyLabelOption="alignLeftOfCenter|hasHintLabel"
+        latin:keyLabelFlags="alignLeftOfCenter|hasHintLabel"
         latin:parentStyle="numKeyStyle" />
     <key-style
         latin:styleName="num0KeyStyle"
-        latin:code="48"
-        latin:keyLabel="0 +"
+        latin:keyLabel="0"
         latin:parentStyle="numberKeyStyle" />
     <key-style
         latin:styleName="num1KeyStyle"
@@ -84,23 +89,46 @@
         latin:keyLabel="9"
         latin:keyHintLabel="WXYZ"
         latin:parentStyle="numberKeyStyle" />
+    <!-- U+002A: "*" ASTERISK
+         U+FF0A: "*" FULLWIDTH ASTERISK -->
     <key-style
         latin:styleName="numStarKeyStyle"
-        latin:code="42"
-        latin:keyLabel="\uff0a"
+        latin:code="0x002A"
+        latin:keyLabel="&#xFF0A;"
         latin:parentStyle="numKeyStyle" />
+    <!-- Only for non-tablet device -->
     <key-style
-        latin:styleName="numSwitchToAltKeyStyle"
-        latin:code="@integer/key_shift"
+        latin:styleName="numPhoneToSymbolKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
         latin:keyLabel="@string/label_to_phone_symbols_key"
         latin:parentStyle="numModeKeyStyle" />
     <key-style
-        latin:styleName="numSwitchToNumericKeyStyle"
-        latin:code="@integer/key_shift"
+        latin:styleName="numPhoneToNumericKeyStyle"
+        latin:code="@integer/key_switch_alpha_symbol"
         latin:keyLabel="@string/label_to_phone_numeric_key"
         latin:parentStyle="numModeKeyStyle" />
+    <!-- U+002C: "," COMMA -->
+    <key-style
+        latin:styleName="numPauseKeyStyle"
+        latin:code="0x002C"
+        latin:keyLabel="@string/label_pause_key"
+        latin:keyLabelFlags="followKeyHintLabelRatio|autoXScale"
+        latin:parentStyle="numKeyBaseStyle" />
+    <!-- U+003B: ";" SEMICOLON -->
+    <key-style
+        latin:styleName="numWaitKeyStyle"
+        latin:code="0x003B"
+        latin:keyLabel="@string/label_wait_key"
+        latin:keyLabelFlags="followKeyHintLabelRatio|autoXScale"
+        latin:parentStyle="numKeyBaseStyle" />
+    <key-style
+        latin:styleName="numTabKeyStyle"
+        latin:keyActionFlags="noKeyPreview"
+        latin:parentStyle="tabKeyStyle" />
     <key-style
         latin:styleName="numSpaceKeyStyle"
         latin:code="@integer/key_space"
-        latin:keyIcon="iconSpaceKey" />
+        latin:keyIcon="iconSpaceKeyForNumberLayout"
+        latin:keyActionFlags="enableLongPress"
+        latin:parentStyle="numKeyBaseStyle" />
 </merge>
diff --git a/java/res/xml/keyboard_set.xml b/java/res/xml/keyboard_set.xml
new file mode 100644
index 0000000..8966ddb
--- /dev/null
+++ b/java/res/xml/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml/kbd_phone_shift.xml b/java/res/xml/keys_curly_brackets.xml
similarity index 72%
copy from java/res/xml/kbd_phone_shift.xml
copy to java/res/xml/keys_curly_brackets.xml
index eea823f..d21a092 100644
--- a/java/res/xml/kbd_phone_shift.xml
+++ b/java/res/xml/keys_curly_brackets.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,13 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="26.67%p"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
-</Keyboard>
+    <Key
+        latin:keyLabel="{"
+        latin:code="@integer/keycode_for_left_curly_bracket" />
+    <Key
+        latin:keyLabel="}"
+        latin:code="@integer/keycode_for_right_curly_bracket" />
+</merge>
diff --git a/java/res/xml/kbd_phone_shift.xml b/java/res/xml/keys_less_greater.xml
similarity index 65%
copy from java/res/xml/kbd_phone_shift.xml
copy to java/res/xml/keys_less_greater.xml
index eea823f..8961d9c 100644
--- a/java/res/xml/kbd_phone_shift.xml
+++ b/java/res/xml/keys_less_greater.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,15 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="26.67%p"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
-</Keyboard>
+    <Key
+        latin:keyLabel="&lt;"
+        latin:code="@integer/keycode_for_less_than"
+        latin:moreKeys="@string/more_keys_for_less_than" />
+    <Key
+        latin:keyLabel="&gt;"
+        latin:code="@integer/keycode_for_greater_than"
+        latin:moreKeys="@string/more_keys_for_greater_than" />
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_qwerty.xml b/java/res/xml/keys_parentheses.xml
similarity index 64%
copy from java/res/xml-sw768dp/kbd_rows_qwerty.xml
copy to java/res/xml/keys_parentheses.xml
index 6237712..6853bf1 100644
--- a/java/res/xml-sw768dp/kbd_rows_qwerty.xml
+++ b/java/res/xml/keys_parentheses.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -21,14 +21,12 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+    <Key
+        latin:keyLabel="("
+        latin:code="@integer/keycode_for_left_parenthesis"
+        latin:moreKeys="@string/more_keys_for_left_parenthesis" />
+    <Key
+        latin:keyLabel=")"
+        latin:code="@integer/keycode_for_right_parenthesis"
+        latin:moreKeys="@string/more_keys_for_right_parenthesis" />
 </merge>
diff --git a/java/res/xml/kbd_phone_shift.xml b/java/res/xml/keys_square_brackets.xml
similarity index 72%
copy from java/res/xml/kbd_phone_shift.xml
copy to java/res/xml/keys_square_brackets.xml
index eea823f..44387c3 100644
--- a/java/res/xml/kbd_phone_shift.xml
+++ b/java/res/xml/keys_square_brackets.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,13 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="26.67%p"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
-</Keyboard>
+    <Key
+        latin:keyLabel="["
+        latin:code="@integer/keycode_for_left_square_bracket" />
+    <Key
+        latin:keyLabel="]"
+        latin:code="@integer/keycode_for_right_square_bracket" />
+</merge>
diff --git a/java/res/xml/language_prefs.xml b/java/res/xml/language_prefs.xml
deleted file mode 100644
index b7a4c07..0000000
--- a/java/res/xml/language_prefs.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          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.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-        android:title="@string/language_selection_title">
-</PreferenceScreen>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 6184add..7a21a85 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -20,7 +20,51 @@
 <!-- The attributes in this XML file provide configuration information -->
 <!-- for the Input Method Manager. -->
 
-<!-- Keyboard: en_US, en_GB, ar, cs, da, de, de(QWERTY), es, es_US, fi, fr, fr_CA, fr_CH, hr, hu, it, iw, nb, nl, pl, pt, ru, sr, sv, tr -->
+<!-- Supported locales: name/layout
+    ar: Arabic/arabic
+    be: Belarusian/east_slavic
+    bg: Bulgarian/bulgarian
+    cs: Czech/qwertz
+    da: Danish/nordic
+    de: German/qwertz
+    de_QY: German (QWERTY)/qwerty
+    el: Greek/greek
+    en_US: English United States/qwerty
+    en_GB: English Great Britain/qwerty
+    es: Spanish/spanish
+    et: Estonian/nordic
+    fa: Persian/arabic
+    fi: Finnish/nordic
+    fr: French/azerty
+    fr_CA: French Canada/qwerty
+    fr_CH: French Switzerland/qwertz
+    hi: Hindi/hindi
+    hr: Croatian/qwertz
+    hu: Hungarian/qwertz
+    is: Icelandic/qwerty
+    it: Italian/qwerty
+    iw: Hebrew/hebrew
+    ka: Georgian/georgian
+    ky: Kyrgyz/east_slavic
+    lt: Lithuanian/qwerty
+    lv: Latvian/qwerty
+    mk: Macedonian/south_slavic
+    nb: Norwaian Bokmål/nordic
+    nl: Dutch/qwerty
+    pl: Polish/qwerty
+    pt: Portuguese/qwerty
+    ro: Romanian/qwerty
+    ru: Russian/east_slavic
+    sk: Slovak/qwerty
+    sl: Slovenian/qwerty
+    sr: Serbian/south_slavic
+    sv: Swedish/nordic
+    th: Thai/thai
+    tr: Turkish/qwerty
+    uk: Ukrainian/east_slavic
+    vi: Vietnamese/qwerty
+    zz_QY: QWERTY/qwerty
+    -->
 <!-- TODO: use <lang>_keyboard icon instead of a common keyboard icon. -->
 <!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default
      subtype.-->
@@ -31,145 +75,246 @@
             android:label="@string/subtype_en_US"
             android:imeSubtypeLocale="en_US"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_en_GB"
             android:imeSubtypeLocale="en_GB"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="ar"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="be"
+            android:imeSubtypeMode="keyboard"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="bg"
+            android:imeSubtypeMode="keyboard"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="cs"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="da"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="de"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
-            android:label="@string/subtype_de_qwerty"
+            android:label="@string/subtype_generic_qwerty"
             android:imeSubtypeLocale="de"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,KeyboardLocale=de_ZZ,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,KeyboardLocale=de_QY"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="el"
+            android:imeSubtypeMode="keyboard"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="es"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="et"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="fa"
+            android:imeSubtypeMode="keyboard"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fi"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fr_CA"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fr_CH"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="hi"
+            android:imeSubtypeMode="keyboard"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="hr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="hu"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="is"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="it"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <!-- Java uses the deprecated "iw" code instead of the standard "he" code for Hebrew. -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="iw"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="ka"
+            android:imeSubtypeMode="keyboard"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="ky"
+            android:imeSubtypeMode="keyboard"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="lt"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="lv"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="mk"
+            android:imeSubtypeMode="keyboard"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="nb"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="nl"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="pl"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="pt"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="ro"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="ru"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="sk"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="sl"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="sr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="sv"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="tr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="th"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue=""
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="uk"
+            android:imeSubtypeMode="keyboard"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="vi"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_qwerty"
+            android:imeSubtypeLocale="zz_QY"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable"
     />
 </input-method>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index dcaa202..c5c647a 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -21,9 +21,6 @@
     <PreferenceCategory
         android:title="@string/general_category"
         android:key="general_settings">
-        <PreferenceScreen
-            android:key="subtype_settings"
-            android:title="@string/language_selection_title" />
         <CheckBoxPreference
             android:key="auto_cap"
             android:title="@string/auto_cap"
@@ -44,11 +41,6 @@
             android:title="@string/popup_on_keypress"
             android:persistent="true"
             android:defaultValue="@bool/config_default_popup_preview" />
-        <CheckBoxPreference
-            android:key="show_settings_key"
-            android:title="@string/prefs_settings_key"
-            android:persistent="true"
-            android:defaultValue="@bool/config_default_show_settings_key" />
         <ListPreference
             android:key="voice_mode"
             android:title="@string/voice_input"
@@ -97,6 +89,17 @@
             android:key="pref_advanced_settings"
             android:title="@string/advanced_settings"
             android:summary="@string/advanced_settings_summary">
+            <CheckBoxPreference
+                android:key="pref_suppress_language_switch_key"
+                android:title="@string/suppress_language_switch_key"
+                android:persistent="true"
+                android:defaultValue="false" />
+            <CheckBoxPreference
+                android:key="pref_include_other_imes_in_language_switch_list"
+                android:title="@string/include_other_imes_in_language_switch_list"
+                android:summary="@string/include_other_imes_in_language_switch_list_summary"
+                android:persistent="true"
+                android:defaultValue="false" />
             <!-- Values for popup dismiss delay are added programatically -->
             <ListPreference
                 android:key="pref_key_preview_popup_dismiss_delay"
@@ -114,6 +117,13 @@
                 android:persistent="true"
                 android:defaultValue="true" />
             <CheckBoxPreference
+                android:key="bigram_prediction"
+                android:dependency="bigram_suggestion"
+                android:title="@string/bigram_prediction"
+                android:summary="@string/bigram_prediction_summary"
+                android:persistent="true"
+                android:defaultValue="false" />
+            <CheckBoxPreference
                 android:key="enable_span_insert"
                 android:title="@string/enable_span_insert"
                 android:summary="@string/enable_span_insert_summary"
@@ -125,15 +135,6 @@
             <PreferenceScreen
                 android:key="pref_keypress_sound_volume"
                 android:title="@string/prefs_keypress_sound_volume_settings" />
-            <!-- TODO: evaluate results and revive this option. The code
-                already supports it. -->
-            <!-- <CheckBoxPreference -->
-            <!-- android:key="bigram_prediction" -->
-            <!-- android:dependency="bigram_suggestion" -->
-            <!-- android:title="@string/bigram_prediction" -->
-            <!-- android:summary="@string/bigram_prediction_summary" -->
-            <!-- android:persistent="true" -->
-            <!-- android:defaultValue="false" /> -->
         </PreferenceScreen>
     </PreferenceCategory>
 </PreferenceScreen>
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 80613a5..b926ed0 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -32,7 +32,7 @@
             android:persistent="true"
             android:entryValues="@array/keyboard_layout_modes_values"
             android:entries="@array/keyboard_layout_modes"
-            android:defaultValue="@string/config_default_keyboard_theme_id"
+            android:defaultValue="@string/config_default_keyboard_theme_index"
             />
 
     <CheckBoxPreference
@@ -42,4 +42,10 @@
             android:defaultValue="false"
             />
 
+    <CheckBoxPreference
+            android:key="force_non_distinct_multitouch"
+            android:title="@string/prefs_force_non_distinct_multitouch"
+            android:persistent="true"
+            android:defaultValue="false"
+            />
 </PreferenceScreen>
diff --git a/java/res/xml/row_qwerty4.xml b/java/res/xml/row_qwerty4.xml
new file mode 100644
index 0000000..b2b47e9
--- /dev/null
+++ b/java/res/xml/row_qwerty4.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyWidth="15%p" />
+        <switch>
+            <case
+                latin:mode="url"
+            >
+                <Key
+                    latin:keyLabel="/"
+                    latin:keyStyle="f1MoreKeysStyle" />
+            </case>
+            <case
+                latin:mode="email"
+            >
+                <Key
+                    latin:keyLabel="\@"
+                    latin:keyStyle="f1MoreKeysStyle" />
+            </case>
+            <case
+                latin:hasShortcutKey="true"
+            >
+                <Key
+                    latin:keyStyle="shortcutKeyStyle" />
+            </case>
+            <!-- latin:hasShortcutKey="false" -->
+            <default>
+                <Key
+                    latin:keyLabel="@string/keylabel_for_comma"
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:additionalMoreKeys="@string/more_keys_for_comma"
+                    latin:keyStyle="f1MoreKeysStyle" />
+            </default>
+        </switch>
+        <switch>
+            <case
+                latin:languageCode="fa"
+                latin:languageSwitchKeyEnabled="true"
+            >
+                <Key
+                    latin:keyStyle="languageSwitchKeyStyle" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="30%p" />
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <case
+                latin:languageCode="fa"
+                latin:languageSwitchKeyEnabled="false"
+            >
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <case
+                latin:languageSwitchKeyEnabled="true"
+            >
+                <Key
+                    latin:keyStyle="languageSwitchKeyStyle" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
+            </case>
+            <!-- languageSwitchKeyEnabled="false" -->
+            <default>
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="50%p" />
+            </default>
+        </switch>
+        <Key
+            latin:keyStyle="punctuationKeyStyle" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+</merge>
diff --git a/java/res/xml/rowkeys_arabic1.xml b/java/res/xml/rowkeys_arabic1.xml
new file mode 100644
index 0000000..b1bf790
--- /dev/null
+++ b/java/res/xml/rowkeys_arabic1.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0636: "ض" ARABIC LETTER DAD
+         U+0661: "١" ARABIC-INDIC DIGIT ONE -->
+    <Key
+        latin:keyLabel="&#x0636;"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1,&#x0661;" />
+    <!-- U+0635: "ص" ARABIC LETTER SAD
+         U+0662: "٢" ARABIC-INDIC DIGIT TWO -->
+    <Key
+        latin:keyLabel="&#x0635;"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2,&#x0662;" />
+    <!-- U+0642: "ق" ARABIC LETTER QAF
+         U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE
+         U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
+    <Key
+        latin:keyLabel="&#x0642;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3,&#x0663;"
+        latin:moreKeys="&#x06A8;" />
+    <!-- U+0641: "ف" ARABIC LETTER FEH
+         U+06A4: "ڤ" ARABIC LETTER VEH
+         U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
+         U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW
+         U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06A2 ARABIC LETTER FEH WITH DOT MOVED BELOW -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
+    <Key
+        latin:keyLabel="&#x0641;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4,&#x0664;"
+        latin:moreKeys="&#x06A4;,&#x06A2;,&#x06A5;" />
+    <!-- U+063A: "غ" ARABIC LETTER GHAIN
+         U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
+    <Key
+        latin:keyLabel="&#x063A;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5,&#x0665;" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN
+         U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
+    <Key
+        latin:keyLabel="&#x0639;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6,&#x0666;" />
+    <!-- U+0647: "ه" ARABIC LETTER HEH
+         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
+         U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
+         U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x0647;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7,&#x0667;"
+        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;" />
+    <!-- U+062E: "خ" ARABIC LETTER KHAH
+         U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
+    <Key
+        latin:keyLabel="&#x062E;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8,&#x0668;" />
+    <!-- U+062D: "ح" ARABIC LETTER HAH
+         U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
+    <Key
+        latin:keyLabel="&#x062D;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9,&#x0669;" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM
+         U+0686: "چ" ARABIC LETTER TCHEH
+         U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
+    <Key
+        latin:keyLabel="&#x062C;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0,&#x0660;"
+        latin:moreKeys="&#x0686;" />
+</merge>
diff --git a/java/res/xml/rowkeys_arabic2.xml b/java/res/xml/rowkeys_arabic2.xml
new file mode 100644
index 0000000..f86aae0
--- /dev/null
+++ b/java/res/xml/rowkeys_arabic2.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0634: "ش" ARABIC LETTER SHEEN
+         U+069C: "ڜ" ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+069C ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
+    <Key
+        latin:keyLabel="&#x0634;"
+        latin:moreKeys="&#x069C;" />
+    <!-- U+0633: "س" ARABIC LETTER SEEN -->
+    <Key
+        latin:keyLabel="&#x0633;" />
+    <!-- U+064A: "ي" ARABIC LETTER YEH
+         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
+         U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
+    <Key
+        latin:keyLabel="&#x064A;"
+        latin:moreKeys="&#x0626;,&#x0649;" />
+    <!-- U+0628: "ب" ARABIC LETTER BEH
+         U+067E: "پ" ARABIC LETTER PEH -->
+    <Key
+        latin:keyLabel="&#x0628;"
+        latin:moreKeys="&#x067E;" />
+    <!-- U+0644: "ل" ARABIC LETTER LAM
+         U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
+         U+0627: "ا" ARABIC LETTER ALEF
+         U+FEF7: "ﻷ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
+         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
+         U+FEF9: "ﻹ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
+         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
+         U+FEF5: "ﻵ" ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0644;"
+        latin:moreKeys="&#xFEFB;|&#x0644;&#x0627;,&#xFEF7;|&#x0644;&#x0623;,&#xFEF9;|&#x0644;&#x0625;,&#xFEF5;|&#x0644;&#x0622;" />
+    <!-- U+0627: "ا" ARABIC LETTER ALEF
+         U+0621: "ء" ARABIC LETTER HAMZA
+         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
+         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
+         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0627;"
+        latin:moreKeys="&#x0621;,&#x0671;,&#x0623;,&#x0625;,&#x0622;" />
+    <!-- U+062A: "ت" ARABIC LETTER TEH
+         U+062B: "ﺙ" ARABIC LETTER THEH -->
+    <Key
+        latin:keyLabel="&#x062A;"
+        latin:moreKeys="&#x062B;" />
+    <!-- U+0646: "ن" ARABIC LETTER NOON -->
+    <Key
+        latin:keyLabel="&#x0646;" />
+    <!-- U+0645: "م" ARABIC LETTER MEEM -->
+    <Key
+        latin:keyLabel="&#x0645;" />
+    <!-- U+0643: "ك" ARABIC LETTER KAF
+         U+06AF: "گ" ARABIC LETTER GAF
+         U+06A9: "ک" ARABIC LETTER KEHEH -->
+    <Key
+        latin:keyLabel="&#x0643;"
+        latin:moreKeys="&#x06AF;,&#x06A9;"
+        latin:keyWidth="fillRight" />
+</merge>
diff --git a/java/res/xml/rowkeys_arabic3.xml b/java/res/xml/rowkeys_arabic3.xml
new file mode 100644
index 0000000..9e9eac0
--- /dev/null
+++ b/java/res/xml/rowkeys_arabic3.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <Key
+        latin:keyLabel="&#x0638;" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <Key
+        latin:keyLabel="&#x0637;" />
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <Key
+        latin:keyLabel="&#x0630;" />
+    <!-- U+062F: "د" ARABIC LETTER DAL -->
+    <Key
+        latin:keyLabel="&#x062F;" />
+    <!-- U+0632: "ز" ARABIC LETTER ZAIN
+         U+0698: "ژ" ARABIC LETTER JEH -->
+    <Key
+        latin:keyLabel="&#x0632;"
+        latin:moreKeys="&#x0698;" />
+    <!-- U+0631: "ر" ARABIC LETTER REH -->
+    <Key
+        latin:keyLabel="&#x0631;" />
+    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <Key
+        latin:keyLabel="&#x0629;" />
+    <!-- U+0648: "و" ARABIC LETTER WAW
+         U+0624: "ﺅ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0648;"
+        latin:moreKeys="&#x0624;" />
+</merge>
diff --git a/java/res/xml/rowkeys_azerty1.xml b/java/res/xml/rowkeys_azerty1.xml
new file mode 100644
index 0000000..9983432
--- /dev/null
+++ b/java/res/xml/rowkeys_azerty1.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="a"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1"
+        latin:moreKeys="@string/more_keys_for_a" />
+    <Key
+        latin:keyLabel="z"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2"
+        latin:moreKeys="@string/more_keys_for_z" />
+    <Key
+        latin:keyLabel="e"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="@string/more_keys_for_e" />
+    <Key
+        latin:keyLabel="r"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4"
+        latin:moreKeys="@string/more_keys_for_r" />
+    <Key
+        latin:keyLabel="t"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5"
+        latin:moreKeys="@string/more_keys_for_t" />
+    <Key
+        latin:keyLabel="y"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:moreKeys="@string/more_keys_for_y" />
+    <Key
+        latin:keyLabel="u"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7"
+        latin:moreKeys="@string/more_keys_for_u" />
+    <Key
+        latin:keyLabel="i"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="@string/more_keys_for_i" />
+    <Key
+        latin:keyLabel="o"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9"
+        latin:moreKeys="@string/more_keys_for_o" />
+    <Key
+        latin:keyLabel="p"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+</merge>
diff --git a/java/res/xml/rowkeys_azerty2.xml b/java/res/xml/rowkeys_azerty2.xml
new file mode 100644
index 0000000..ff0b062
--- /dev/null
+++ b/java/res/xml/rowkeys_azerty2.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="q" />
+    <Key
+        latin:keyLabel="s"
+        latin:moreKeys="@string/more_keys_for_s" />
+    <Key
+        latin:keyLabel="d"
+        latin:moreKeys="@string/more_keys_for_d" />
+    <Key
+        latin:keyLabel="f" />
+    <Key
+        latin:keyLabel="g"
+        latin:moreKeys="@string/more_keys_for_g" />
+    <Key
+        latin:keyLabel="h"
+        latin:moreKeys="@string/more_keys_for_h" />
+    <Key
+        latin:keyLabel="j"
+        latin:moreKeys="@string/more_keys_for_j" />
+    <Key
+        latin:keyLabel="k"
+        latin:moreKeys="@string/more_keys_for_k" />
+    <Key
+        latin:keyLabel="l"
+        latin:moreKeys="@string/more_keys_for_l" />
+    <Key
+        latin:keyLabel="m" />
+</merge>
diff --git a/java/res/xml/rowkeys_azerty3.xml b/java/res/xml/rowkeys_azerty3.xml
new file mode 100644
index 0000000..b81c3c5
--- /dev/null
+++ b/java/res/xml/rowkeys_azerty3.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="w"
+        latin:moreKeys="@string/more_keys_for_w" />
+    <Key
+        latin:keyLabel="x" />
+    <Key
+        latin:keyLabel="c"
+        latin:moreKeys="@string/more_keys_for_c" />
+    <Key
+        latin:keyLabel="v"
+        latin:moreKeys="@string/more_keys_for_v" />
+    <Key
+        latin:keyLabel="b" />
+    <Key
+        latin:keyLabel="n"
+        latin:moreKeys="@string/more_keys_for_n" />
+    <include
+        latin:keyboardLayout="@xml/key_azerty_quote" />
+</merge>
diff --git a/java/res/xml/rowkeys_bulgarian1.xml b/java/res/xml/rowkeys_bulgarian1.xml
new file mode 100644
index 0000000..adeecaf
--- /dev/null
+++ b/java/res/xml/rowkeys_bulgarian1.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0447: "ч" CYRILLIC SMALL LETTER CHE -->
+    <Key
+        latin:keyLabel="&#x0447;"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <!-- U+0448: "ш" CYRILLIC SMALL LETTER SHA -->
+    <Key
+        latin:keyLabel="&#x0448;"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2" />
+    <!-- U+0435: "е" CYRILLIC SMALL LETTER IE -->
+    <Key
+        latin:keyLabel="&#x0435;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3" />
+    <!-- U+0440: "р" CYRILLIC SMALL LETTER ER -->
+    <Key
+        latin:keyLabel="&#x0440;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4" />
+    <!-- U+0442: "т" CYRILLIC SMALL LETTER TE -->
+    <Key
+        latin:keyLabel="&#x0442;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5" />
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <Key
+        latin:keyLabel="&#x044A;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6" />
+    <!-- U+0443: "у" CYRILLIC SMALL LETTER U -->
+    <Key
+        latin:keyLabel="&#x0443;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7" />
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I
+         U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE -->
+    <Key
+        latin:keyLabel="&#x0438;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="&#x045D;" />
+    <!-- U+043E: "о" CYRILLIC SMALL LETTER O -->
+    <Key
+        latin:keyLabel="&#x043E;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9" />
+    <!-- U+043F: "п" CYRILLIC SMALL LETTER PE -->
+    <Key
+        latin:keyLabel="&#x043F;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+    <!-- U+044F: "я" CYRILLIC SMALL LETTER YA -->
+    <Key
+        latin:keyLabel="&#x044F;" />
+</merge>
diff --git a/java/res/xml/rowkeys_bulgarian2.xml b/java/res/xml/rowkeys_bulgarian2.xml
new file mode 100644
index 0000000..599edd3
--- /dev/null
+++ b/java/res/xml/rowkeys_bulgarian2.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0430: "а" CYRILLIC SMALL LETTER A -->
+    <Key
+        latin:keyLabel="&#x0430;" />
+    <!-- U+0441: "с" CYRILLIC SMALL LETTER ES -->
+    <Key
+        latin:keyLabel="&#x0441;" />
+    <!-- U+0434: "д" CYRILLIC SMALL LETTER DE -->
+    <Key
+        latin:keyLabel="&#x0434;" />
+    <!-- U+0444: "ф" CYRILLIC SMALL LETTER EF -->
+    <Key
+        latin:keyLabel="&#x0444;" />
+    <!-- U+0433: "г" CYRILLIC SMALL LETTER GHE -->
+    <Key
+        latin:keyLabel="&#x0433;" />
+    <!-- U+0445: "х" CYRILLIC SMALL LETTER HA -->
+    <Key
+        latin:keyLabel="&#x0445;" />
+    <!-- U+0439: "й" CYRILLIC SMALL LETTER SHORT I -->
+    <Key
+        latin:keyLabel="&#x0439;" />
+    <!-- U+043A: "к" CYRILLIC SMALL LETTER KA -->
+    <Key
+        latin:keyLabel="&#x043A;" />
+    <!-- U+043B: "л" CYRILLIC SMALL LETTER EL -->
+    <Key
+        latin:keyLabel="&#x043B;" />
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <Key
+        latin:keyLabel="&#x0449;" />
+    <!-- U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN -->
+    <Key
+        latin:keyLabel="&#x044C;" />
+</merge>
diff --git a/java/res/xml/rowkeys_bulgarian3.xml b/java/res/xml/rowkeys_bulgarian3.xml
new file mode 100644
index 0000000..19872cd
--- /dev/null
+++ b/java/res/xml/rowkeys_bulgarian3.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE -->
+    <Key
+        latin:keyLabel="&#x0437;" />
+    <!-- U+0436: "ж" CYRILLIC SMALL LETTER ZHE -->
+    <Key
+        latin:keyLabel="&#x0436;" />
+    <!-- U+0446: "ц" CYRILLIC SMALL LETTER TSE -->
+    <Key
+        latin:keyLabel="&#x0446;" />
+    <!-- U+0432: "в" CYRILLIC SMALL LETTER VE -->
+    <Key
+        latin:keyLabel="&#x0432;" />
+    <!-- U+0431: "б" CYRILLIC SMALL LETTER BE -->
+    <Key
+        latin:keyLabel="&#x0431;" />
+    <!-- U+043D: "н" CYRILLIC SMALL LETTER EN -->
+    <Key
+        latin:keyLabel="&#x043D;" />
+    <!-- U+043C: "м" CYRILLIC SMALL LETTER EM -->
+    <Key
+        latin:keyLabel="&#x043C;" />
+    <!-- U+044E: "ю" CYRILLIC SMALL LETTER YU -->
+    <Key
+        latin:keyLabel="&#x044E;" />
+</merge>
diff --git a/java/res/xml/rowkeys_east_slavic1.xml b/java/res/xml/rowkeys_east_slavic1.xml
new file mode 100644
index 0000000..04c6ef6
--- /dev/null
+++ b/java/res/xml/rowkeys_east_slavic1.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0439: "й" CYRILLIC SMALL LETTER SHORT I -->
+    <Key
+        latin:keyLabel="&#x0439;"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <!-- U+0446: "ц" CYRILLIC SMALL LETTER TSE -->
+    <Key
+        latin:keyLabel="&#x0446;"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2" />
+    <!-- U+0443: "у" CYRILLIC SMALL LETTER U -->
+    <Key
+        latin:keyLabel="&#x0443;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="@string/more_keys_for_cyrillic_u" />
+    <!-- U+043A: "к" CYRILLIC SMALL LETTER KA -->
+    <Key
+        latin:keyLabel="&#x043A;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4" />
+    <!-- U+0435: "е" CYRILLIC SMALL LETTER IE -->
+    <Key
+        latin:keyLabel="&#x0435;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5"
+        latin:moreKeys="@string/more_keys_for_cyrillic_ye" />
+    <!-- U+043D: "н" CYRILLIC SMALL LETTER EN -->
+    <Key
+        latin:keyLabel="&#x043D;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:moreKeys="@string/more_keys_for_cyrillic_en" />
+    <!-- U+0433: "г" CYRILLIC SMALL LETTER GHE -->
+    <Key
+        latin:keyLabel="&#x0433;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7" />
+    <!-- U+0448: "ш" CYRILLIC SMALL LETTER SHA -->
+    <Key
+        latin:keyLabel="&#x0448;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_east_slavic_row1_9"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9" />
+    <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE -->
+    <Key
+        latin:keyLabel="&#x0437;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+    <!-- U+0445: "х" CYRILLIC SMALL LETTER HA -->
+    <Key
+        latin:keyLabel="&#x0445;"
+        latin:moreKeys="@string/more_keys_for_cyrillic_ha" />
+</merge>
diff --git a/java/res/xml/rowkeys_east_slavic2.xml b/java/res/xml/rowkeys_east_slavic2.xml
new file mode 100644
index 0000000..57b0373
--- /dev/null
+++ b/java/res/xml/rowkeys_east_slavic2.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0444: "ф" CYRILLIC SMALL LETTER EF -->
+    <Key
+        latin:keyLabel="&#x0444;" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_east_slavic_row2_1"
+        latin:moreKeys="@string/more_keys_for_east_slavic_row2_1" />
+    <!-- U+0432: "в" CYRILLIC SMALL LETTER VE -->
+    <Key
+        latin:keyLabel="&#x0432;" />
+    <!-- U+0430: "а" CYRILLIC SMALL LETTER A -->
+    <Key
+        latin:keyLabel="&#x0430;" />
+    <!-- U+043F: "п" CYRILLIC SMALL LETTER PE -->
+    <Key
+        latin:keyLabel="&#x043F;" />
+    <!-- U+0440: "р" CYRILLIC SMALL LETTER ER -->
+    <Key
+        latin:keyLabel="&#x0440;" />
+    <!-- U+043E: "о" CYRILLIC SMALL LETTER O -->
+    <Key
+        latin:keyLabel="&#x043E;"
+        latin:moreKeys="@string/more_keys_for_cyrillic_o" />
+    <!-- U+043B: "л" CYRILLIC SMALL LETTER EL -->
+    <Key
+        latin:keyLabel="&#x043B;" />
+    <!-- U+0434: "д" CYRILLIC SMALL LETTER DE -->
+    <Key
+        latin:keyLabel="&#x0434;" />
+    <!-- U+0436: "ж" CYRILLIC SMALL LETTER ZHE -->
+    <Key
+        latin:keyLabel="&#x0436;" />
+    <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
+    <Key
+        latin:keyLabel="&#x044D;" />
+</merge>
diff --git a/java/res/xml/rowkeys_east_slavic3.xml b/java/res/xml/rowkeys_east_slavic3.xml
new file mode 100644
index 0000000..b0f7aed
--- /dev/null
+++ b/java/res/xml/rowkeys_east_slavic3.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+044F: "я" CYRILLIC SMALL LETTER YA -->
+    <Key
+        latin:keyLabel="&#x044F;" />
+    <!-- U+0447: "ч" CYRILLIC SMALL LETTER CHE -->
+    <Key
+        latin:keyLabel="&#x0447;" />
+    <!-- U+0441: "с" CYRILLIC SMALL LETTER ES -->
+    <Key
+        latin:keyLabel="&#x0441;" />
+    <!-- U+043C: "м" CYRILLIC SMALL LETTER EM -->
+    <Key
+        latin:keyLabel="&#x043C;" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_east_slavic_row3_5" />
+    <!-- U+0442: "т" CYRILLIC SMALL LETTER TE -->
+    <Key
+        latin:keyLabel="&#x0442;" />
+    <!-- U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN -->
+    <Key
+        latin:keyLabel="&#x044C;"
+        latin:moreKeys="@string/more_keys_for_cyrillic_soft_sign" />
+    <!-- U+0431: "б" CYRILLIC SMALL LETTER BE -->
+    <Key
+        latin:keyLabel="&#x0431;" />
+    <!-- U+044E: "ю" CYRILLIC SMALL LETTER YU -->
+    <Key
+        latin:keyLabel="&#x044E;" />
+</merge>
diff --git a/java/res/xml/rowkeys_farsi1.xml b/java/res/xml/rowkeys_farsi1.xml
new file mode 100644
index 0000000..15cb801
--- /dev/null
+++ b/java/res/xml/rowkeys_farsi1.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0635: "ص" ARABIC LETTER SAD
+         U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE -->
+    <Key
+        latin:keyLabel="&#x0635;"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1,&#x06F1;" />
+    <!-- U+0642: "ق" ARABIC LETTER QAF
+         U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO -->
+    <Key
+        latin:keyLabel="&#x0642;"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2,&#x06F2;" />
+    <!-- U+0641: "ف" ARABIC LETTER FEH
+         U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE -->
+    <Key
+        latin:keyLabel="&#x0641;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3,&#x06F3;" />
+    <!-- U+063A: "غ" ARABIC LETTER GHAIN
+         U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR -->
+    <Key
+        latin:keyLabel="&#x063A;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4,&#x06F4;" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN
+         U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE -->
+    <Key
+        latin:keyLabel="&#x0639;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5,&#x06F5;" />
+    <!-- U+0647: "ه" ARABIC LETTER HEH
+         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
+         U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
+         U+06C0: "ۀ" ARABIC LETTER HEH WITH YEH ABOVE
+         U+0629: "ة" ARABIC LETTER TEH MARBUTA
+         U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+06C0 ARABIC LETTER HEH WITH YEH ABOVE -->
+    <Key
+        latin:keyLabel="&#x0647;"
+        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;,&#x06C0;,&#x0629;,%"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6,&#x06F6;" />
+    <!-- U+062E: "خ" ARABIC LETTER KHAH
+         U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x062E;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7,&#x06F7;" />
+    <!-- U+062D: "ح" ARABIC LETTER HAH
+         U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
+    <Key
+        latin:keyLabel="&#x062D;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8,&#x06F8;" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM
+         U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE -->
+    <Key
+        latin:keyLabel="&#x062C;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9,&#x06F9;" />
+    <!-- U+0686: "چ" ARABIC LETTER TCHEH
+         U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO -->
+    <Key
+        latin:keyLabel="&#x0686;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0,&#x06F0;" />
+</merge>
diff --git a/java/res/xml/rowkeys_farsi2.xml b/java/res/xml/rowkeys_farsi2.xml
new file mode 100644
index 0000000..77279c6
--- /dev/null
+++ b/java/res/xml/rowkeys_farsi2.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0634: "ش" ARABIC LETTER SHEEN -->
+    <Key
+        latin:keyLabel="&#x0634;" />
+    <!-- U+0633: "س" ARABIC LETTER SEEN
+         U+0636: "ض" ARABIC LETTER DAD -->
+    <Key
+        latin:keyLabel="&#x0633;"
+        latin:moreKeys="&#x0636;" />
+    <!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
+         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
+         U+064A: "ي" ARABIC LETTER YEH -->
+    <Key
+        latin:keyLabel="&#x06CC;"
+        latin:moreKeys="&#x0626;,&#x064A;" />
+    <!-- U+0628: "ب" ARABIC LETTER BEH -->
+    <Key
+        latin:keyLabel="&#x0628;" />
+    <!-- U+0644: "ل" ARABIC LETTER LAM -->
+    <Key
+        latin:keyLabel="&#x0644;" />
+    <!-- U+0627: "ا" ARABIC LETTER ALEF
+         U+0621: "ء" ARABIC LETTER HAMZA
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
+         U+0672: "ٲ" ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE
+         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
+         U+0673: "ٳ" ARABIC LETTER ALEF WITH WAVY HAMZA BELOW-->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+0672 ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+0673 ARABIC LETTER ALEF WITH WAVY HAMZA BELOW -->
+    <Key
+        latin:keyLabel="&#x0627;"
+        latin:moreKeys="&#x0621;,&#x0622;,&#x0672;,&#x0671;,&#x0673;" />
+    <!-- U+062A: "ت" ARABIC LETTER TEH
+         U+062B: "ﺙ" ARABIC LETTER THEH -->
+    <Key
+        latin:keyLabel="&#x062A;"
+        latin:moreKeys="&#x062B;" />
+    <!-- U+0646: "ن" ARABIC LETTER NOON -->
+    <Key
+        latin:keyLabel="&#x0646;" />
+    <!-- U+0645: "م" ARABIC LETTER MEEM -->
+    <Key
+        latin:keyLabel="&#x0645;" />
+    <!-- U+06A9: "ک" ARABIC LETTER KEHEH
+         U+0643: "ك" ARABIC LETTER KAF -->
+    <Key
+        latin:keyLabel="&#x06A9;"
+        latin:moreKeys="&#x0643;" />
+</merge>
diff --git a/java/res/xml/rowkeys_farsi3.xml b/java/res/xml/rowkeys_farsi3.xml
new file mode 100644
index 0000000..8db56e3
--- /dev/null
+++ b/java/res/xml/rowkeys_farsi3.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0637: "ط" ARABIC LETTER TAH
+         U+0638: "ظ" ARABIC LETTER ZAH -->
+    <Key
+        latin:keyLabel="&#x0637;"
+        latin:moreKeys="&#x0638;" />
+    <!-- U+0698: "ژ" ARABIC LETTER JEH -->
+    <Key
+        latin:keyLabel="&#x0698;" />
+    <!-- U+0632: "ز" ARABIC LETTER ZAIN -->
+    <Key
+        latin:keyLabel="&#x0632;" />
+    <!-- U+0631: "ر" ARABIC LETTER REH -->
+    <Key
+        latin:keyLabel="&#x0631;" />
+    <!-- U+062F: "د" ARABIC LETTER DAL
+         U+0630: "ذ" ARABIC LETTER THAL -->
+    <Key
+        latin:keyLabel="&#x062F;"
+        latin:moreKeys="&#x0630;" />
+    <!-- U+067E: "پ" ARABIC LETTER PEH -->
+    <Key
+        latin:keyLabel="&#x067E;" />
+    <!-- U+0648: "و" ARABIC LETTER WAW
+         U+0676: "ٶ" ARABIC LETTER HIGH HAMZA WAW -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+0676 ARABIC LETTER HIGH HAMZA WAW -->
+    <Key
+        latin:keyLabel="&#x0648;"
+        latin:moreKeys="&#x0676;" />
+    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
+    <Key
+        latin:keyLabel="&#x06AF;" />
+</merge>
diff --git a/java/res/xml/rowkeys_georgian1.xml b/java/res/xml/rowkeys_georgian1.xml
new file mode 100644
index 0000000..6b24c29
--- /dev/null
+++ b/java/res/xml/rowkeys_georgian1.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <Key
+                latin:keyLabel="Q"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1" />
+            <!-- U+10ED: "ჭ" GEORGIAN LETTER CHAR -->
+            <Key
+                latin:keyLabel="&#x10ED;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+            <Key
+                latin:keyLabel="E"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3" />
+            <!-- U+10E6: "ღ" GEORGIAN LETTER GHAN -->
+            <Key
+                latin:keyLabel="&#x10E6;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4" />
+            <!-- U+10D7: "თ" GEORGIAN LETTER TAN -->
+            <Key
+                latin:keyLabel="&#x10D7;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5" />
+            <Key
+                latin:keyLabel="Y"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6" />
+            <Key
+                latin:keyLabel="U"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7" />
+            <Key
+                latin:keyLabel="I"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8" />
+            <Key
+                latin:keyLabel="O"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9" />
+            <Key
+                latin:keyLabel="P"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0" />
+        </case>
+        <default>
+            <!-- U+10E5: "ქ" GEORGIAN LETTER GHAN -->
+            <Key
+                latin:keyLabel="&#x10E5;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1" />
+            <!-- U+10EC: "წ" GEORGIAN LETTER CIL -->
+            <Key
+                latin:keyLabel="&#x10EC;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+            <!-- U+10D4: "ე" GEORGIAN LETTER EN
+                 U+10F1: "ჱ" GEORGIAN LETTER HE -->
+            <Key
+                latin:keyLabel="&#x10D4;"
+                latin:moreKeys="&#x10F1;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3" />
+            <!-- U+10E0: "რ" GEORGIAN LETTER RAE -->
+            <Key
+                latin:keyLabel="&#x10E0;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4" />
+            <!-- U+10E2: "ტ" GEORGIAN LETTER TAR -->
+            <Key
+                latin:keyLabel="&#x10E2;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5" />
+            <!-- U+10E7: "ყ" GEORGIAN LETTER QAR
+                 U+10F8: "ჸ" GEORGIAN LETTER ELIFI -->
+            <Key
+                latin:keyLabel="&#x10E7;"
+                latin:moreKeys="&#x10F8;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6" />
+            <!-- U+10E3: "უ" GEORGIAN LETTER UN -->
+            <Key
+                latin:keyLabel="&#x10E3;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7" />
+            <!-- U+10D8: "ი" GEORGIAN LETTER IN
+                 U+10F2: "ჲ" GEORGIAN LETTER HIE -->
+            <Key
+                latin:keyLabel="&#x10D8;"
+                latin:moreKeys="&#x10F2;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8" />
+            <!-- U+10DD: "ო" GEORGIAN LETTER ON -->
+            <Key
+                latin:keyLabel="&#x10DD;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9" />
+            <!-- U+10DE: "პ" GEORGIAN LETTER PAR -->
+            <Key
+                latin:keyLabel="&#x10DE;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_georgian2.xml b/java/res/xml/rowkeys_georgian2.xml
new file mode 100644
index 0000000..f50e3d6
--- /dev/null
+++ b/java/res/xml/rowkeys_georgian2.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <Key
+                latin:keyLabel="A"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10E8: "შ" GEORGIAN LETTER SHIN -->
+            <Key
+                latin:keyLabel="&#x10E8;"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="D"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="F"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="G"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="H"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10DF: "ჟ" GEORGIAN LETTER ZHAR -->
+            <Key
+                latin:keyLabel="&#x10DF;"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="K"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="L"
+                latin:keyLabelFlags="preserveCase" />
+        </case>
+        <default>
+            <!-- U+10D0: "ა" GEORGIAN LETTER AN
+                 U+10FA: "ჺ" GEORGIAN LETTER AIN -->
+            <Key
+                latin:keyLabel="&#x10D0;"
+                latin:moreKeys="&#x10FA;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10E1: "ს" GEORGIAN LETTER SAN -->
+            <Key
+                latin:keyLabel="&#x10E1;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10D3: "დ" GEORGIAN LETTER DON -->
+            <Key
+                latin:keyLabel="&#x10D3;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10E4: "ფ" GEORGIAN LETTER PHAR
+                 U+10F6: "ჶ" GEORGIAN LETTER FI -->
+            <Key
+                latin:keyLabel="&#x10E4;"
+                latin:moreKeys="&#x10F6;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10D2: "გ" GEORGIAN LETTER GAN
+                 U+10F9: "ჹ" GEORGIAN LETTER TURNED GAN -->
+            <Key
+                latin:keyLabel="&#x10D2;"
+                latin:moreKeys="&#x10F9;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10F0: "ჰ" GEORGIAN LETTER HAE
+                 U+10F5: "ჵ" GEORGIAN LETTER HOE -->
+            <Key
+                latin:keyLabel="&#x10F0;"
+                latin:moreKeys="&#x10F5;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10EF: "ჯ" GEORGIAN LETTER JHAN
+                 U+10F7: "ჷ" GEORGIAN LETTER YN -->
+            <Key
+                latin:keyLabel="&#x10EF;"
+                latin:moreKeys="&#x10F7;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10D9: "კ" GEORGIAN LETTER KAN -->
+            <Key
+                latin:keyLabel="&#x10D9;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10DA: "ლ" GEORGIAN LETTER LAS -->
+            <Key
+                latin:keyLabel="&#x10DA;"
+                latin:keyLabelFlags="preserveCase" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_georgian3.xml b/java/res/xml/rowkeys_georgian3.xml
new file mode 100644
index 0000000..f908673
--- /dev/null
+++ b/java/res/xml/rowkeys_georgian3.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+10EB: "ძ" GEORGIAN LETTER JIL -->
+            <Key
+                latin:keyLabel="&#x10EB;"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="X"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10E9: "ჩ" GEORGIAN LETTER CHIN -->
+            <Key
+                latin:keyLabel="&#x10E9;"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="V"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="B"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="N"
+                latin:keyLabelFlags="preserveCase" />
+            <Key
+                latin:keyLabel="M"
+                latin:keyLabelFlags="preserveCase" />
+        </case>
+        <default>
+            <!-- U+10D6: "ზ" GEORGIAN LETTER ZEN -->
+            <Key
+                latin:keyLabel="&#x10D6;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10EE: "ხ" GEORGIAN LETTER XAN
+                 U+10F4: "ჴ" GEORGIAN LETTER HAR -->
+            <Key
+                latin:keyLabel="&#x10EE;"
+                latin:moreKeys="&#x10F4;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10EA: "ც" GEORGIAN LETTER CAN -->
+            <Key
+                latin:keyLabel="&#x10EA;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10D5: "ვ" GEORGIAN LETTER VIN
+                 U+10F3: "ჳ" GEORGIAN LETTER WE -->
+            <Key
+                latin:keyLabel="&#x10D5;"
+                latin:moreKeys="&#x10F3;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10D1: "ბ" GEORGIAN LETTER BAN -->
+            <Key
+                latin:keyLabel="&#x10D1;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10DC: "ნ" GEORGIAN LETTER NAR
+                 U+10FC: "ჼ" MODIFIER LETTER GEORGIAN NAR -->
+            <Key
+                latin:keyLabel="&#x10DC;"
+                latin:moreKeys="&#x10FC;"
+                latin:keyLabelFlags="preserveCase" />
+            <!-- U+10DB: "მ" GEORGIAN LETTER MAN -->
+            <Key
+                latin:keyLabel="&#x10DB;"
+                latin:keyLabelFlags="preserveCase" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_greek1.xml b/java/res/xml/rowkeys_greek1.xml
new file mode 100644
index 0000000..4df49f8
--- /dev/null
+++ b/java/res/xml/rowkeys_greek1.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- TODO: Should find a way to compound Greek dialytika tonos and other Greek letters. -->
+    <!--
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            U+0385: "΅" GREEK DIALYTIKA TONOS
+            <Key
+                latin:keyLabel="&#x0385;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+        </case>
+        <default>
+        -->
+            <!-- U+03C2: "ς" GREEK SMALL LETTER FINAL SIGMA -->
+            <Key
+                latin:keyLabel="&#x03C2;"
+                latin:keyLabelFlags="preserveCase"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+    <!--
+        </default>
+    </switch>
+        -->
+    <!-- U+03B5: "ε" GREEK SMALL LETTER EPSILON
+         U+03AD: "έ" GREEK SMALL LETTER EPSILON WITH TONOS -->
+    <Key
+        latin:keyLabel="&#x03B5;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="&#x03AD;" />
+    <!-- U+03C1: "ρ" GREEK SMALL LETTER RHO -->
+    <Key
+        latin:keyLabel="&#x03C1;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4" />
+    <!-- U+03C4: "τ" GREEK SMALL LETTER TAU -->
+    <Key
+        latin:keyLabel="&#x03C4;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5" />
+    <!-- U+03C5: "υ" GREEK SMALL LETTER UPSILON
+         U+03CD: "ύ" GREEK SMALL LETTER UPSILON WITH TONOS
+         U+03CB: "ϋ" GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+         U+03B0: "ΰ" GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS -->
+    <Key
+        latin:keyLabel="&#x03C5;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:moreKeys="&#x03CD;,&#x03CB;,&#x03B0;" />
+    <!-- U+03B8: "θ" GREEK SMALL LETTER THETA -->
+    <Key
+        latin:keyLabel="&#x03B8;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7" />
+    <!-- U+03B9: "ι" GREEK SMALL LETTER IOTA
+         U+03AF: "ί" GREEK SMALL LETTER IOTA WITH TONOS
+         U+03CA: "ϊ" GREEK SMALL LETTER IOTA WITH DIALYTIKA
+         U+0390: "ΐ" GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS -->
+    <Key
+        latin:keyLabel="&#x03B9;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="&#x03AF;,&#x03CA;,&#x0390;" />
+    <!-- U+03BF: "ο" GREEK SMALL LETTER OMICRON
+         U+03CC: "ό" GREEK SMALL LETTER OMICRON WITH TONOS -->
+    <Key
+        latin:keyLabel="&#x03BF;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9"
+        latin:moreKeys="&#x03CC;" />
+    <!-- U+03C0: "π" GREEK SMALL LETTER PI -->
+    <Key
+        latin:keyLabel="&#x03C0;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+</merge>
diff --git a/java/res/xml/rowkeys_greek2.xml b/java/res/xml/rowkeys_greek2.xml
new file mode 100644
index 0000000..91bdc11
--- /dev/null
+++ b/java/res/xml/rowkeys_greek2.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+03B1: "α" GREEK SMALL LETTER ALPHA
+         U+03AC: "ά" GREEK SMALL LETTER ALPHA WITH TONOS -->
+    <Key
+        latin:keyLabel="&#x03B1;"
+        latin:moreKeys="&#x03AC;" />
+    <!-- U+03C3: "σ" GREEK SMALL LETTER SIGMA -->
+    <Key
+        latin:keyLabel="&#x03C3;" />
+    <!-- U+03B4: "δ" GREEK SMALL LETTER DELTA -->
+    <Key
+        latin:keyLabel="&#x03B4;" />
+    <!-- U+03C6: "φ" GREEK SMALL LETTER PHI -->
+    <Key
+        latin:keyLabel="&#x03C6;" />
+    <!-- U+03B3: "γ" GREEK SMALL LETTER GAMMA -->
+    <Key
+        latin:keyLabel="&#x03B3;" />
+    <!-- U+03B7: "η" GREEK SMALL LETTER ETA
+         U+03AE: "ή" GREEK SMALL LETTER ETA WITH TONOS -->
+    <Key
+        latin:keyLabel="&#x03B7;"
+        latin:moreKeys="&#x03AE;" />
+    <!-- U+03BE: "ξ" GREEK SMALL LETTER XI -->
+    <Key
+        latin:keyLabel="&#x03BE;" />
+    <!-- U+03BA: "κ" GREEK SMALL LETTER KAPPA -->
+    <Key
+        latin:keyLabel="&#x03BA;" />
+    <!-- U+03BB: "λ" GREEK SMALL LETTER LAMDA -->
+    <Key
+        latin:keyLabel="&#x03BB;" />
+</merge>
diff --git a/java/res/xml/rowkeys_greek3.xml b/java/res/xml/rowkeys_greek3.xml
new file mode 100644
index 0000000..8a99db9
--- /dev/null
+++ b/java/res/xml/rowkeys_greek3.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+03B6: "ζ" GREEK SMALL LETTER ZETA -->
+    <Key
+        latin:keyLabel="&#x03B6;" />
+    <!-- U+03C7: "χ" GREEK SMALL LETTER CHI -->
+    <Key
+        latin:keyLabel="&#x03C7;" />
+    <!-- U+03C8: "ψ" GREEK SMALL LETTER PSI -->
+    <Key
+        latin:keyLabel="&#x03C8;" />
+    <!-- U+03C9: "ω" GREEK SMALL LETTER OMEGA
+         U+03CE: "ώ" GREEK SMALL LETTER OMEGA WITH TONOS -->
+    <Key
+        latin:keyLabel="&#x03C9;"
+        latin:moreKeys="&#x03CE;" />
+    <!-- U+03B2: "β" GREEK SMALL LETTER BETA -->
+    <Key
+        latin:keyLabel="&#x03B2;" />
+    <!-- U+03BD: "ν" GREEK SMALL LETTER NU -->
+    <Key
+        latin:keyLabel="&#x03BD;" />
+    <!-- U+03BC: "μ" GREEK SMALL LETTER MU -->
+    <Key
+        latin:keyLabel="&#x03BC;" />
+</merge>
diff --git a/java/res/xml/rowkeys_hebrew1.xml b/java/res/xml/rowkeys_hebrew1.xml
new file mode 100644
index 0000000..396da78
--- /dev/null
+++ b/java/res/xml/rowkeys_hebrew1.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+05E7: "ק" HEBREW LETTER QOF -->
+    <Key
+        latin:keyLabel="&#x05E7;" />
+    <!-- U+05E8: "ר" HEBREW LETTER RESH -->
+    <Key
+        latin:keyLabel="&#x05E8;" />
+    <!-- U+05D0: "א" HEBREW LETTER ALEF -->
+    <Key
+        latin:keyLabel="&#x05D0;" />
+    <!-- U+05D8: "ט" HEBREW LETTER TET -->
+    <Key
+        latin:keyLabel="&#x05D8;" />
+    <!-- U+05D5: "ו" HEBREW LETTER VAV -->
+    <Key
+        latin:keyLabel="&#x05D5;" />
+    <!-- U+05DF: "ן" HEBREW LETTER FINAL NUN -->
+    <Key
+        latin:keyLabel="&#x05DF;" />
+    <!-- U+05DD: "ם" HEBREW LETTER FINAL MEM -->
+    <Key
+        latin:keyLabel="&#x05DD;" />
+    <!-- U+05E4: "פ" HEBREW LETTER PE -->
+    <Key
+        latin:keyLabel="&#x05E4;" />
+</merge>
diff --git a/java/res/xml/rowkeys_hebrew2.xml b/java/res/xml/rowkeys_hebrew2.xml
new file mode 100644
index 0000000..e4ecac3
--- /dev/null
+++ b/java/res/xml/rowkeys_hebrew2.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+05E9: "ש" HEBREW LETTER SHIN -->
+    <Key
+        latin:keyLabel="&#x05E9;" />
+    <!-- U+05D3: "ד" HEBREW LETTER DALET -->
+    <Key
+        latin:keyLabel="&#x05D3;" />
+    <!-- U+05D2: "ג" HEBREW LETTER GIMEL
+         U+05D2 U+05F3: "ג׳" HEBREW LETTER GIMEL + HEBREW PUNCTUATION GERESH -->
+    <Key
+        latin:keyLabel="&#x05D2;"
+        latin:moreKeys="&#x05D2;&#x05F3;" />
+    <!-- U+05DB: "כ" HEBREW LETTER KAF -->
+    <Key
+        latin:keyLabel="&#x05DB;" />
+    <!-- U+05E2: "ע" HEBREW LETTER AYIN -->
+    <Key
+        latin:keyLabel="&#x05E2;" />
+    <!-- U+05D9: "י" HEBREW LETTER YOD
+         U+05F2 U+05B7: "ײַ" HEBREW LIGATURE YIDDISH DOUBLE YOD + HEBREW POINT PATAH -->
+    <Key
+        latin:keyLabel="&#x05D9;"
+        latin:moreKeys="&#x05F2;&#x05B7;" />
+    <!-- U+05D7: "ח" HEBREW LETTER HET
+         U+05D7 U+05F3: "ח׳" HEBREW LETTER HET + HEBREW PUNCTUATION GERESH -->
+    <Key
+        latin:keyLabel="&#x05D7;"
+        latin:moreKeys="&#x05D7;&#x05F3;" />
+    <!-- U+05DC: "ל" HEBREW LETTER LAMED -->
+    <Key
+        latin:keyLabel="&#x05DC;" />
+    <!-- U+05DA: "ך" HEBREW LETTER FINAL KAF -->
+    <Key
+        latin:keyLabel="&#x05DA;" />
+    <!-- U+05E3: "ף" HEBREW LETTER FINAL PE -->
+    <Key
+        latin:keyLabel="&#x05E3;" />
+</merge>
diff --git a/java/res/xml/rowkeys_hebrew3.xml b/java/res/xml/rowkeys_hebrew3.xml
new file mode 100644
index 0000000..805a7a5
--- /dev/null
+++ b/java/res/xml/rowkeys_hebrew3.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+05D6: "ז" HEBREW LETTER ZAYIN
+         U+05D6 U+05F3: "ז׳" HEBREW LETTER ZAYIN + HEBREW PUNCTUATION GERESH -->
+    <Key
+        latin:keyLabel="&#x05D6;"
+        latin:moreKeys="&#x05D6;&#x05F3;" />
+    <!-- U+05E1: "ס" HEBREW LETTER SAMEKH -->
+    <Key
+        latin:keyLabel="&#x05E1;" />
+    <!-- U+05D1: "ב" HEBREW LETTER BET -->
+    <Key
+        latin:keyLabel="&#x05D1;" />
+    <!-- U+05D4: "ה" HEBREW LETTER HE -->
+    <Key
+        latin:keyLabel="&#x05D4;" />
+    <!-- U+05E0: "נ" HEBREW LETTER NUN -->
+    <Key
+        latin:keyLabel="&#x05E0;" />
+    <!-- U+05DE: "מ" HEBREW LETTER MEM -->
+    <Key
+        latin:keyLabel="&#x05DE;" />
+    <!-- U+05E6: "צ" HEBREW LETTER TSADI
+         U+05E6 U+05F3: "צ׳" HEBREW LETTER TSADI + HEBREW PUNCTUATION GERESH -->
+    <Key
+        latin:keyLabel="&#x05E6;"
+        latin:moreKeys="&#x05E6;&#x05F3;" />
+    <!-- U+05EA: "ת" HEBREW LETTER TAV
+         U+05EA U+05F3: "ת׳" HEBREW LETTER TAV + HEBREW PUNCTUATION GERESH -->
+    <Key
+        latin:keyLabel="&#x05EA;"
+        latin:moreKeys="&#x05EA;&#x05F3;" />
+    <!-- U+05E5: "ץ" HEBREW LETTER FINAL TSADI
+         U+05E5 U+05F3: "ץ׳" HEBREW LETTER FINAL TSADI + HEBREW PUNCTUATION GERESH -->
+    <Key
+        latin:keyLabel="&#x05E5;"
+        latin:moreKeys="&#x05E5;&#x05F3;" />
+</merge>
diff --git a/java/res/xml/rowkeys_hindi1.xml b/java/res/xml/rowkeys_hindi1.xml
new file mode 100644
index 0000000..eb99de9
--- /dev/null
+++ b/java/res/xml/rowkeys_hindi1.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0914: "औ" DEVANAGARI LETTER AU
+                 U+0912/U+0902: "ऒं" DEVANAGARI LETTER SHORT O//DEVANAGARI SIGN ANUSVARA -->
+            <Key
+                latin:keyLabel="&#x0914;"
+                latin:moreKeys="&#x0912;&#x0902;" />
+            <!-- U+0910: "ऐ" DEVANAGARI LETTER AI
+                 U+0910/U+0902: "ऐं" DEVANAGARI LETTER AI/DEVANAGARI SIGN ANUSVARA -->
+            <Key
+                latin:keyLabel="&#x0910;"
+                latin:moreKeys="&#x0910;&#x0902;" />
+            <!-- U+0906: "आ" DEVANAGARI LETTER AA
+                 U+0906/U+0902: "आं" DEVANAGARI LETTER AA/DEVANAGARI SIGN ANUSVARA
+                 U+0906/U+0901: "आँ" DEVANAGARI LETTER AA/DEVANAGARI SIGN CANDRABINDU -->
+            <Key
+                latin:keyLabel="&#x0906;"
+                latin:moreKeys="&#x0906;&#x0902;,&#x0906;&#x0901;" />
+            <!-- U+0908: "ई" DEVANAGARI LETTER II
+                 U+0908/U+0902: "ईं" DEVANAGARI LETTER II/DEVANAGARI SIGN ANUSVARA -->
+            <Key
+                latin:keyLabel="&#x0908;"
+                latin:moreKeys="&#x0908;&#x0902;" />
+            <!-- U+090A: "ऊ" DEVANAGARI LETTER UU
+                 U+090A/U+0902: "ऊं" DEVANAGARI LETTER UU/DEVANAGARI SIGN ANUSVARA
+                 U+090A/U+0901: "ऊँ" DEVANAGARI LETTER UU/DEVANAGARI SIGN CANDRABINDU -->
+            <Key
+                latin:keyLabel="&#x090A;"
+                latin:moreKeys="&#x090A;&#x0902;,&#x090A;&#x0901;" />
+            <!-- U+092D: "भ" DEVANAGARI LETTER BHA -->
+            <Key
+                latin:keyLabel="&#x092D;" />
+            <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+            <Key
+                latin:keyLabel="&#x0903;" />
+            <!-- U+0918: "घ" DEVANAGARI LETTER GHA -->
+            <Key
+                latin:keyLabel="&#x0918;" />
+            <!-- U+0927: "ध" DEVANAGARI LETTER DHA
+                 U+0915/U+094D/U+0937: "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA
+                 U+0936/U+094D/U+0930: "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA -->
+            <Key
+                latin:keyLabel="&#x0927;"
+                latin:moreKeys="&#x0915;&#x094D;&#x0937;,&#x0936;&#x094D;&#x0930;" />
+            <!-- U+091D: "झ" DEVANAGARI LETTER JHA -->
+            <Key
+                latin:keyLabel="&#x091D;" />
+            <!-- U+0922: "ढ" DEVANAGARI LETTER DDHA -->
+            <Key
+                latin:keyLabel="&#x0922;" />
+        </case>
+        <default>
+            <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
+                 U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA
+                 U+0967: "१" DEVANAGARI DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x094C;"
+                latin:moreKeys="&#x094C;&#x0902;,%"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="&#x0967;,1" />
+            <!-- U+0948: "ै" DEVANAGARI VOWEL SIGN AI
+                 U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
+                 U+0968: "२" DEVANAGARI DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0948;"
+                latin:moreKeys="&#x0948;&#x0902;,%"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="&#x0968;,2" />
+            <!-- U+093E: "ा" DEVANAGARI VOWEL SIGN AA
+                 U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
+                 U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
+                 U+0969: "३" DEVANAGARI DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x093E;"
+                latin:moreKeys="&#x093E;&#x0902;,&#x093E;&#x0901;,%"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="&#x0969;,3" />
+            <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II
+                 U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
+                 U+096A: "४" DEVANAGARI DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0940;"
+                latin:moreKeys="&#x0940;&#x0902;,%"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="&#x096A;,4" />
+            <!-- U+0942: "ू" DEVANAGARI VOWEL SIGN UU
+                 U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
+                 U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
+                 U+096B: "५" DEVANAGARI DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0942;"
+                latin:moreKeys="&#x0942;&#x0902;,&#x0942;&#x0901;,%"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="&#x096B;,5" />
+            <!-- U+092C: "ब" DEVANAGARI LETTER BA
+                 U+092C/U+0952: "ब॒" DEVANAGARI LETTER BA/DEVANAGARI STRESS SIGN ANUDATTA -->
+            <Key
+                latin:keyLabel="&#x092C;"
+                latin:moreKeys="&#x092C;&#x0952;,%"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="&#x096C;,6" />
+            <!-- U+0939: "ह" DEVANAGARI LETTER HA
+                 U+096D: "७" DEVANAGARI DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0939;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="&#x096D;,7" />
+            <!-- U+0917: "ग" DEVANAGARI LETTER GA
+                 U+091C/U+094D/U+091E: "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA
+                 U+0917/U+093C: "ग़" DEVANAGARI LETTER GA/DEVANAGARI SIGN NUKTA
+                 U+0917/U+0952: "ग॒" DEVANAGARI LETTER GA/DEVANAGARI STRESS SIGN ANUDATTA
+                 U+096E: "८" DEVANAGARI DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0917;"
+                latin:moreKeys="&#x091C;&#x094D;&#x091E;,&#x0917;&#x093C;,&#x0917;&#x0952;,%"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="&#x096E;,8" />
+            <!-- U+0926: "द" DEVANAGARI LETTER DA
+                 U+096F: "९" DEVANAGARI DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x0926;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9" />
+            <!-- U+091C: "ज" DEVANAGARI LETTER JA
+                 U+091C/U+0952: "ज॒" DEVANAGARI LETTER JA/DEVANAGARI STRESS SIGN ANUDATTA
+                 U+091C/U+094D/U+091E: "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA
+                 U+091C/U+093C: "ज़" DEVANAGARI LETTER JA/DEVANAGARI SIGN NUKTA
+                 U+0966: "०" DEVANAGARI DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x091C;"
+                latin:moreKeys="&#x091C;&#x0952;,&#x091C;&#x094D;&#x091E;,&#x091C;&#x093C;,%"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="&#x0966;,0" />
+            <!-- U+0921: "ड" DEVANAGARI LETTER DDA
+                 U+0921/U+0952: "ड॒" DEVANAGARI LETTER DDA/DEVANAGARI STRESS SIGN ANUDATTA
+                 U+0921/U+093C: "ड़" DEVANAGARI LETTER DDA/DEVANAGARI SIGN NUKTA -->
+            <Key
+                latin:keyLabel="&#x0921;"
+                latin:moreKeys="&#x0921;&#x0952;,&#x0921;&#x093C;" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_hindi2.xml b/java/res/xml/rowkeys_hindi2.xml
new file mode 100644
index 0000000..95f4881
--- /dev/null
+++ b/java/res/xml/rowkeys_hindi2.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0913: "ओ" DEVANAGARI LETTER O
+                 U+0913/U+0902: "ओं" DEVANAGARI LETTER O/DEVANAGARI SIGN ANUSVARA
+                 U+0911: "ऑ" DEVANAGARI LETTER CANDRA O
+                 U+0912: "ऒ" DEVANAGARI LETTER SHORT O -->
+            <Key
+                latin:keyLabel="&#x0913;"
+                latin:moreKeys="&#x0913;&#x0902;,&#x0911;,&#x0912;" />
+            <!-- U+090F: "ए" DEVANAGARI LETTER E
+                 U+090F/U+0902: "एं" DEVANAGARI LETTER E/DEVANAGARI SIGN ANUSVARA
+                 U+090F/U+0901: "एँ" DEVANAGARI LETTER E/DEVANAGARI SIGN CANDRABINDU
+                 U+090D: "ऍ" DEVANAGARI LETTER CANDRA E
+                 U+090E: "ऎ" DEVANAGARI LETTER SHORT E -->
+            <Key
+                latin:keyLabel="&#x090F;"
+                latin:moreKeys="&#x090F;&#x0902;,&#x090F;&#x0901;,&#x090D;,&#x090E;" />
+            <!-- U+0905: "अ" DEVANAGARI LETTER A
+                 U+0905/U+0902: "अं" DEVANAGARI LETTER A/DEVANAGARI SIGN ANUSVARA
+                 U+0905/U+0901: "अँ" DEVANAGARI LETTER A/DEVANAGARI SIGN CANDRABINDU -->
+            <Key
+                latin:keyLabel="&#x0905;"
+                latin:moreKeys="&#x0905;&#x0902;,&#x0905;&#x0901;" />
+            <!-- U+0907: "इ" DEVANAGARI LETTER I
+                 U+0907/U+0902: "इं" DEVANAGARI LETTER I/DEVANAGARI SIGN ANUSVARA
+                 U+0907/U+0901: "इं" DEVANAGARI LETTER I/DEVANAGARI SIGN CANDRABINDU -->
+            <Key
+                latin:keyLabel="&#x0907;"
+                latin:moreKeys="&#x0907;&#x0902;,&#x0907;&#x0901;" />
+            <!-- U+0909: "उ" DEVANAGARI LETTER U
+                 U+0909/U+0902: "उं" DEVANAGARI LETTER U/DEVANAGARI SIGN ANUSVARA
+                 U+0909/U+0901: "उँ" DEVANAGARI LETTER U/DEVANAGARI SIGN CANDRABINDU -->
+            <Key
+                latin:keyLabel="&#x0909;"
+                latin:moreKeys="&#x0909;&#x0902;,&#x0909;&#x0901;" />
+            <!-- U+092B: "फ" DEVANAGARI LETTER PHA
+                 U+092B/U+093C: "फ़" DEVANAGARI LETTER PHA/DEVANAGARI SIGN NUKTA -->
+            <Key
+                latin:keyLabel="&#x092B;"
+                latin:moreKeys="&#x092B;&#x093C;" />
+            <!-- U+0931: "ऱ" DEVANAGARI LETTER RRA
+                 U+094D/U+0930: "्र" DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA
+                 U+0930/U+094D: "र्" DEVANAGARI LETTER RA/DEVANAGARI SIGN VIRAMA -->
+            <Key
+                latin:keyLabel="&#x0931;"
+                latin:moreKeys="&#x094D;&#x0930;,&#x0930;&#x094D;" />
+            <!-- U+0916: "ख" DEVANAGARI LETTER KHA
+                 U+0916/U+093C: "ख़" DEVANAGARI LETTER KHA/DEVANAGARI SIGN NUKTA -->
+            <Key
+                latin:keyLabel="&#x0916;"
+                latin:moreKeys="&#x0916;&#x093C;" />
+            <!-- U+0925: "थ" DEVANAGARI LETTER THA -->
+            <Key
+                latin:keyLabel="&#x0925;" />
+            <!-- U+091B: "छ" DEVANAGARI LETTER CHA -->
+            <Key
+                latin:keyLabel="&#x091B;" />
+            <!-- U+0920: "ठ" DEVANAGARI LETTER TTHA -->
+            <Key
+                latin:keyLabel="&#x0920;" />
+        </case>
+        <default>
+            <!-- U+094B: "ो" DEVANAGARI VOWEL SIGN O
+                 U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
+                 U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
+                 U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
+            <Key
+                latin:keyLabel="&#x094B;"
+                latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;" />
+            <!-- U+0947: "े" DEVANAGARI VOWEL SIGN E
+                 U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
+            <Key
+                latin:keyLabel="&#x0947;"
+                latin:moreKeys="&#x0947;&#x0902;" />
+            <!-- U+094D: "्" DEVANAGARI SIGN VIRAMA -->
+            <Key
+                latin:keyLabel="&#x094D;" />
+            <!-- U+093F: "ि" DEVANAGARI VOWEL SIGN I
+                 U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
+            <Key
+                latin:keyLabel="&#x093F;"
+                latin:moreKeys="&#x093F;&#x0902;" />
+            <!-- U+0941: "ु" DEVANAGARI VOWEL SIGN U
+                 U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
+                 U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
+            <Key
+                latin:keyLabel="&#x0941;"
+                latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;" />
+            <!-- U+092A: "प" DEVANAGARI LETTER PA -->
+            <Key
+                latin:keyLabel="&#x092A;" />
+            <!-- U+0930: "र" DEVANAGARI LETTER RA
+                 U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R
+                 U+0930/U+093C: "ऱ" DEVANAGARI LETTER RA/DEVANAGARI SIGN NUKTA
+                 U+0960: "ॠ" DEVANAGARI LETTER VOCALIC RR -->
+            <Key
+                latin:keyLabel="&#x0930;"
+                latin:moreKeys="&#x090B;,&#x0930;&#x093C;,&#x0960;" />
+            <!-- U+0915: "क" DEVANAGARI LETTER KA
+                 U+0915/U+093C: "क़" DEVANAGARI LETTER KA/DEVANAGARI SIGN NUKTA -->
+            <Key
+                latin:keyLabel="&#x0915;"
+                latin:moreKeys="&#x0915;&#x093C;" />
+            <!-- U+0924: "त" DEVANAGARI LETTER TA
+                 U+0924/U+094D/U+0930: "त्र" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA -->
+            <Key
+                latin:keyLabel="&#x0924;"
+                latin:moreKeys="&#x0924;&#x094D;&#x0930;" />
+            <!-- U+091A: "च" DEVANAGARI LETTER CA -->
+            <Key
+                latin:keyLabel="&#x091A;" />
+            <!-- U+091F: "ट" DEVANAGARI LETTER TTA -->
+            <Key
+                latin:keyLabel="&#x091F;" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_hindi3.xml b/java/res/xml/rowkeys_hindi3.xml
new file mode 100644
index 0000000..c023115
--- /dev/null
+++ b/java/res/xml/rowkeys_hindi3.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0911: "ऑ" DEVANAGARI LETTER CANDRA O -->
+            <Key
+                latin:keyLabel="&#x0911;" />
+            <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
+                 U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E-->
+            <Key
+                latin:keyLabel="&#x0901;"
+                latin:moreKeys="&#x0945;" />
+            <!-- U+0923: "ण" DEVANAGARI LETTER NNA -->
+            <Key
+                latin:keyLabel="&#x0923;" />
+            <!-- U+0929: "ऩ" DEVANAGARI LETTER NNNA -->
+            <Key
+                latin:keyLabel="&#x0929;" />
+            <!-- U+0933: "ळ" DEVANAGARI LETTER LLA
+                 U+0934: "ऴ" DEVANAGARI LETTER LLLA -->
+            <Key
+                latin:keyLabel="&#x0933;"
+                latin:moreKeys="&#x0934;" />
+            <!-- U+0936: "श" DEVANAGARI LETTER SHA -->
+            <Key
+                latin:keyLabel="&#x0936;" />
+            <!-- U+0937: "ष" DEVANAGARI LETTER SSA -->
+            <Key
+                latin:keyLabel="&#x0937;" />
+            <!-- U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
+                 U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
+            <Key
+                latin:keyLabel="&#x0943;"
+                latin:moreKeys="&#x0944;" />
+            <!-- U+091E: "ञ" DEVANAGARI LETTER NYA -->
+            <Key
+                latin:keyLabel="&#x091E;" />
+        </case>
+        <default>
+            <!-- U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
+            <Key
+                latin:keyLabel="&#x0949;" />
+            <!-- U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
+            <Key
+                latin:keyLabel="&#x0902;" />
+            <!-- U+092E: "म" DEVANAGARI LETTER MA
+                 U+0950: "ॐ" DEVANAGARI OM -->
+            <Key
+                latin:keyLabel="&#x092E;"
+                latin:moreKeys="&#x0950;" />
+            <!-- U+0928: "न" DEVANAGARI LETTER NA
+                 U+091E: "ञ" DEVANAGARI LETTER NYA
+                 U+0919: "ङ" DEVANAGARI LETTER NGA
+                 U+0928/U+093C: "ऩ" DEVANAGARI LETTER NA/DEVANAGARI SIGN NUKTA -->
+            <Key
+                latin:keyLabel="&#x0928;"
+                latin:moreKeys="&#x091E;,&#x0919;,&#x0928;&#x093C;" />
+            <!-- U+0935: "व" DEVANAGARI LETTER VA -->
+            <Key
+                latin:keyLabel="&#x0935;" />
+            <!-- U+0932: "ल" DEVANAGARI LETTER LA
+                 U+090C: "ऌ" DEVANAGARI LETTER VOCALIC L
+                 U+0961: "ॡ" DEVANAGARI LETTER VOCALIC LL -->
+            <Key
+                latin:keyLabel="&#x0932;"
+                latin:moreKeys="&#x090C;,&#x0961;" />
+            <!-- U+0938: "स" DEVANAGARI LETTER SA -->
+            <Key
+                latin:keyLabel="&#x0938;" />
+            <!-- U+092F: "य" DEVANAGARI LETTER YA
+                 U+095F: "य़" DEVANAGARI LETTER YYA -->
+            <Key
+                latin:keyLabel="&#x092F;"
+                latin:moreKeys="&#x095F;" />
+            <!-- U+093C: "़" DEVANAGARI SIGN NUKTA
+                 U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
+                 U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
+                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+            <Key
+                latin:keyLabel="&#x093C;"
+                latin:moreKeys="&#x097D;,&#x0970;,&#x093D;" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/rowkeys_nordic1.xml
similarity index 78%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/rowkeys_nordic1.xml
index e29d9ab..056895f 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/rowkeys_nordic1.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,11 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
+        latin:keyboardLayout="@xml/rowkeys_qwerty1" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_nordic_row1_11" />
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_qwerty.xml b/java/res/xml/rowkeys_nordic2.xml
similarity index 65%
rename from java/res/xml-sw768dp/kbd_rows_qwerty.xml
rename to java/res/xml/rowkeys_nordic2.xml
index 6237712..0033ea1 100644
--- a/java/res/xml-sw768dp/kbd_rows_qwerty.xml
+++ b/java/res/xml/rowkeys_nordic2.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -22,13 +22,11 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row1" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row2" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row3" />
-    <include
-        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+        latin:keyboardLayout="@xml/rowkeys_qwerty2" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_nordic_row2_10"
+        latin:moreKeys="@string/more_keys_for_nordic_row2_10" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_nordic_row2_11"
+        latin:moreKeys="@string/more_keys_for_nordic_row2_11" />
 </merge>
diff --git a/java/res/xml/rowkeys_qwerty1.xml b/java/res/xml/rowkeys_qwerty1.xml
new file mode 100644
index 0000000..19067a7
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty1.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="q"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <Key
+        latin:keyLabel="w"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2"
+        latin:moreKeys="@string/more_keys_for_w" />
+    <Key
+        latin:keyLabel="e"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="@string/more_keys_for_e" />
+    <Key
+        latin:keyLabel="r"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4"
+        latin:moreKeys="@string/more_keys_for_r" />
+    <Key
+        latin:keyLabel="t"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5"
+        latin:moreKeys="@string/more_keys_for_t" />
+    <Key
+        latin:keyLabel="y"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:moreKeys="@string/more_keys_for_y" />
+    <Key
+        latin:keyLabel="u"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7"
+        latin:moreKeys="@string/more_keys_for_u" />
+    <Key
+        latin:keyLabel="i"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="@string/more_keys_for_i" />
+    <Key
+        latin:keyLabel="o"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9"
+        latin:moreKeys="@string/more_keys_for_o" />
+    <Key
+        latin:keyLabel="p"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty2.xml b/java/res/xml/rowkeys_qwerty2.xml
new file mode 100644
index 0000000..2fa8214
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty2.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="a"
+        latin:moreKeys="@string/more_keys_for_a" />
+    <Key
+        latin:keyLabel="s"
+        latin:moreKeys="@string/more_keys_for_s" />
+    <Key
+        latin:keyLabel="d"
+        latin:moreKeys="@string/more_keys_for_d" />
+    <Key
+        latin:keyLabel="f" />
+    <Key
+        latin:keyLabel="g"
+        latin:moreKeys="@string/more_keys_for_g" />
+    <Key
+        latin:keyLabel="h"
+        latin:moreKeys="@string/more_keys_for_h" />
+    <Key
+        latin:keyLabel="j"
+        latin:moreKeys="@string/more_keys_for_j" />
+    <Key
+        latin:keyLabel="k"
+        latin:moreKeys="@string/more_keys_for_k" />
+    <Key
+        latin:keyLabel="l"
+        latin:moreKeys="@string/more_keys_for_l" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty3.xml b/java/res/xml/rowkeys_qwerty3.xml
new file mode 100644
index 0000000..932ea6f
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty3.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="z"
+        latin:moreKeys="@string/more_keys_for_z" />
+    <Key
+        latin:keyLabel="x" />
+    <Key
+        latin:keyLabel="c"
+        latin:moreKeys="@string/more_keys_for_c" />
+    <Key
+        latin:keyLabel="v"
+        latin:moreKeys="@string/more_keys_for_v" />
+    <Key
+        latin:keyLabel="b" />
+    <Key
+        latin:keyLabel="n"
+        latin:moreKeys="@string/more_keys_for_n" />
+    <Key
+        latin:keyLabel="m" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwertz1.xml b/java/res/xml/rowkeys_qwertz1.xml
new file mode 100644
index 0000000..3e11a7a
--- /dev/null
+++ b/java/res/xml/rowkeys_qwertz1.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="q"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <Key
+        latin:keyLabel="w"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2"
+        latin:moreKeys="@string/more_keys_for_w" />
+    <Key
+        latin:keyLabel="e"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="@string/more_keys_for_e" />
+    <Key
+        latin:keyLabel="r"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4"
+        latin:moreKeys="@string/more_keys_for_r" />
+    <Key
+        latin:keyLabel="t"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5"
+        latin:moreKeys="@string/more_keys_for_t" />
+    <Key
+        latin:keyLabel="z"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:moreKeys="@string/more_keys_for_z" />
+     <Key
+        latin:keyLabel="u"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7"
+        latin:moreKeys="@string/more_keys_for_u" />
+    <Key
+        latin:keyLabel="i"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="@string/more_keys_for_i" />
+    <Key
+        latin:keyLabel="o"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9"
+        latin:moreKeys="@string/more_keys_for_o" />
+    <Key
+        latin:keyLabel="p"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwertz3.xml b/java/res/xml/rowkeys_qwertz3.xml
new file mode 100644
index 0000000..d37cee6
--- /dev/null
+++ b/java/res/xml/rowkeys_qwertz3.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="y"
+        latin:moreKeys="@string/more_keys_for_y" />
+    <Key
+        latin:keyLabel="x" />
+    <Key
+        latin:keyLabel="c"
+        latin:moreKeys="@string/more_keys_for_c" />
+    <Key
+        latin:keyLabel="v"
+        latin:moreKeys="@string/more_keys_for_v" />
+    <Key
+        latin:keyLabel="b" />
+    <Key
+        latin:keyLabel="n"
+        latin:moreKeys="@string/more_keys_for_n" />
+    <Key
+        latin:keyLabel="m" />
+</merge>
diff --git a/java/res/xml/rowkeys_south_slavic1.xml b/java/res/xml/rowkeys_south_slavic1.xml
new file mode 100644
index 0000000..e3cb89c
--- /dev/null
+++ b/java/res/xml/rowkeys_south_slavic1.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0459: "љ" CYRILLIC SMALL LETTER LJE -->
+    <Key
+        latin:keyLabel="&#x0459;"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <!-- U+045A: "њ" CYRILLIC SMALL LETTER NJE -->
+    <Key
+        latin:keyLabel="&#x045A;"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2" />
+    <!-- U+0435: "е" CYRILLIC SMALL LETTER IE -->
+    <Key
+        latin:keyLabel="&#x0435;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="@string/more_keys_for_cyrillic_ie" />
+    <!-- U+0440: "р" CYRILLIC SMALL LETTER ER -->
+    <Key
+        latin:keyLabel="&#x0440;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4" />
+    <!-- U+0442: "т" CYRILLIC SMALL LETTER TE -->
+    <Key
+        latin:keyLabel="&#x0442;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_south_slavic_row1_6"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6" />
+    <!-- U+0443: "у" CYRILLIC SMALL LETTER U -->
+    <Key
+        latin:keyLabel="&#x0443;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7" />
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <Key
+        latin:keyLabel="&#x0438;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="@string/more_keys_for_cyrillic_i" />
+    <!-- U+043E: "о" CYRILLIC SMALL LETTER O -->
+    <Key
+        latin:keyLabel="&#x043E;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9" />
+    <!-- U+043F: "п" CYRILLIC SMALL LETTER PE -->
+    <Key
+        latin:keyLabel="&#x043F;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+    <!-- U+0448: "ш" CYRILLIC SMALL LETTER SHA -->
+    <Key
+        latin:keyLabel="&#x0448;" />
+</merge>
diff --git a/java/res/xml/rowkeys_south_slavic2.xml b/java/res/xml/rowkeys_south_slavic2.xml
new file mode 100644
index 0000000..5a7ecd4
--- /dev/null
+++ b/java/res/xml/rowkeys_south_slavic2.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0430: "а" CYRILLIC SMALL LETTER A -->
+    <Key
+        latin:keyLabel="&#x0430;" />
+    <!-- U+0441: "с" CYRILLIC SMALL LETTER ES -->
+    <Key
+        latin:keyLabel="&#x0441;" />
+    <!-- U+0434: "д" CYRILLIC SMALL LETTER DE -->
+    <Key
+        latin:keyLabel="&#x0434;" />
+    <!-- U+0444: "ф" CYRILLIC SMALL LETTER EF -->
+    <Key
+        latin:keyLabel="&#x0444;" />
+    <!-- U+0433: "г" CYRILLIC SMALL LETTER GHE -->
+    <Key
+        latin:keyLabel="&#x0433;" />
+    <!-- U+0445: "х" CYRILLIC SMALL LETTER HA -->
+    <Key
+        latin:keyLabel="&#x0445;" />
+    <!-- U+0458: "ј" CYRILLIC SMALL LETTER JE -->
+    <Key
+        latin:keyLabel="&#x0458;" />
+    <!-- U+043A: "к" CYRILLIC SMALL LETTER KA -->
+    <Key
+        latin:keyLabel="&#x043A;" />
+    <!-- U+043B: "л" CYRILLIC SMALL LETTER EL -->
+    <Key
+        latin:keyLabel="&#x043B;" />
+    <!-- U+0447: "ч" CYRILLIC SMALL LETTER CHE -->
+    <Key
+        latin:keyLabel="&#x0447;" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_south_slavic_row2_11" />
+</merge>
diff --git a/java/res/xml/rowkeys_south_slavic3.xml b/java/res/xml/rowkeys_south_slavic3.xml
new file mode 100644
index 0000000..97ff51e
--- /dev/null
+++ b/java/res/xml/rowkeys_south_slavic3.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="@string/keylabel_for_south_slavic_row3_1" />
+    <!-- U+045F: "џ" CYRILLIC SMALL LETTER DZHE -->
+    <Key
+        latin:keyLabel="&#x045F;" />
+    <!-- U+0446: "ц" CYRILLIC SMALL LETTER TSE -->
+    <Key
+        latin:keyLabel="&#x0446;" />
+    <!-- U+0432: "в" CYRILLIC SMALL LETTER VE -->
+    <Key
+        latin:keyLabel="&#x0432;" />
+    <!-- U+0431: "б" CYRILLIC SMALL LETTER BE -->
+    <Key
+        latin:keyLabel="&#x0431;" />
+    <!-- U+043D: "н" CYRILLIC SMALL LETTER EN -->
+    <Key
+        latin:keyLabel="&#x043D;" />
+    <!-- U+043C: "м" CYRILLIC SMALL LETTER EM -->
+    <Key
+        latin:keyLabel="&#x043C;" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_south_slavic_row3_8" />
+    <!-- U+0436: "ж" CYRILLIC SMALL LETTER ZHE -->
+    <Key
+        latin:keyLabel="&#x0436;" />
+</merge>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml/rowkeys_spanish2.xml
similarity index 75%
copy from java/res/xml-sv/kbd_qwerty.xml
copy to java/res/xml/rowkeys_spanish2.xml
index e29d9ab..4c7e579 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml/rowkeys_spanish2.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,12 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sv"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
+        latin:keyboardLayout="@xml/rowkeys_qwerty2" />
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
+    <Key
+        latin:keyLabel="&#x00F1;" />
+ </merge>
diff --git a/java/res/xml/rowkeys_symbols1.xml b/java/res/xml/rowkeys_symbols1.xml
new file mode 100644
index 0000000..f6d6243
--- /dev/null
+++ b/java/res/xml/rowkeys_symbols1.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_1"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_1"
+        latin:moreKeys="@string/more_keys_for_symbols_1" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_2"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_2"
+        latin:moreKeys="@string/more_keys_for_symbols_2" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_3"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_3"
+        latin:moreKeys="@string/more_keys_for_symbols_3" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_4"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_4"
+        latin:moreKeys="@string/more_keys_for_symbols_4" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_5"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_5"
+        latin:moreKeys="@string/more_keys_for_symbols_5" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_6"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_6"
+        latin:moreKeys="@string/more_keys_for_symbols_6" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_7"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_7"
+        latin:moreKeys="@string/more_keys_for_symbols_7" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_8"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_8"
+        latin:moreKeys="@string/more_keys_for_symbols_8" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_9"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_9"
+        latin:moreKeys="@string/more_keys_for_symbols_9" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_0"
+        latin:additionalMoreKeys="@string/additional_more_keys_for_symbols_0"
+        latin:moreKeys="@string/more_keys_for_symbols_0" />
+</merge>
diff --git a/java/res/xml/rowkeys_symbols2.xml b/java/res/xml/rowkeys_symbols2.xml
new file mode 100644
index 0000000..1092421
--- /dev/null
+++ b/java/res/xml/rowkeys_symbols2.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="\@" />
+    <Key
+        latin:keyLabel="\#" />
+    <Key
+        latin:keyStyle="currencyKeyStyle" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_percent"
+        latin:moreKeys="@string/more_keys_for_symbols_percent" />
+    <Key
+        latin:keyLabel="&amp;" />
+    <Key
+        latin:keyLabel="*"
+        latin:moreKeys="@string/more_keys_for_star" />
+    <!-- U+2013: "–" EN DASH
+             U+2014: "—" EM DASH -->
+    <Key
+        latin:keyLabel="-"
+        latin:moreKeys="_,&#x2013;,&#x2014;" />
+    <Key
+        latin:keyLabel="+"
+        latin:moreKeys="@string/more_keys_for_plus" />
+    <include
+        latin:keyboardLayout="@xml/keys_parentheses" />
+</merge>
diff --git a/java/res/xml/rowkeys_symbols3.xml b/java/res/xml/rowkeys_symbols3.xml
new file mode 100644
index 0000000..1a484d4
--- /dev/null
+++ b/java/res/xml/rowkeys_symbols3.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyStyle="toMoreSymbolKeyStyle"
+        latin:keyWidth="15%p"
+        latin:visualInsetsRight="1%p" />
+    <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
+    <Key
+        latin:keyLabel="!"
+        latin:moreKeys="&#x00A1;" />
+    <Key
+        latin:keyLabel="&quot;"
+        latin:moreKeys="@string/more_keys_for_double_quote" />
+    <Key
+        latin:keyLabel="\'"
+        latin:moreKeys="@string/more_keys_for_single_quote" />
+    <Key
+        latin:keyLabel=":" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_semicolon"
+        latin:moreKeys="@string/more_keys_for_symbols_semicolon" />
+    <Key
+        latin:keyLabel="/" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_symbols_question"
+        latin:moreKeys="@string/more_keys_for_symbols_question" />
+    <Key
+        latin:keyStyle="deleteKeyStyle"
+        latin:keyWidth="fillRight"
+        latin:visualInsetsLeft="1%p" />
+</merge>
diff --git a/java/res/xml/rowkeys_symbols_shift1.xml b/java/res/xml/rowkeys_symbols_shift1.xml
new file mode 100644
index 0000000..cc00aeb
--- /dev/null
+++ b/java/res/xml/rowkeys_symbols_shift1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyLabel="~" />
+    <Key
+        latin:keyLabel="`" />
+    <Key
+        latin:keyLabel="|" />
+    <!-- U+2022: "•" BULLET -->
+    <Key
+        latin:keyLabel="&#x2022;"
+        latin:moreKeys="@string/more_keys_for_bullet" />
+    <!-- U+221A: "√" SQUARE ROOT -->
+    <Key
+        latin:keyLabel="&#x221A;" />
+    <!-- U+03C0: "π" GREEK SMALL LETTER PI -->
+    <Key
+        latin:keyLabel="&#x03C0;"
+        latin:moreKeys="Π" />
+    <!-- U+00F7: "÷" DIVISION SIGN -->
+    <Key
+        latin:keyLabel="&#x00F7;" />
+    <!-- U+00D7: "×" MULTIPLICATION SIGN -->
+    <Key
+        latin:keyLabel="&#x00D7;" />
+    <include
+        latin:keyboardLayout="@xml/keys_curly_brackets" />
+</merge>
diff --git a/java/res/xml/rowkeys_symbols_shift2.xml b/java/res/xml/rowkeys_symbols_shift2.xml
new file mode 100644
index 0000000..3fd8aac
--- /dev/null
+++ b/java/res/xml/rowkeys_symbols_shift2.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyStyle="nonSpecialBackgroundTabKeyStyle" />
+    <Key
+        latin:keyStyle="moreCurrency1KeyStyle" />
+    <Key
+        latin:keyStyle="moreCurrency2KeyStyle" />
+    <Key
+        latin:keyStyle="moreCurrency3KeyStyle" />
+    <!-- U+00B0: "°" DEGREE SIGN
+         U+2032: "′" PRIME
+         U+2033: "″" DOUBLE PRIME -->
+    <Key
+        latin:keyLabel="&#x00B0;"
+        latin:moreKeys="&#x2032;,&#x2033;" />
+    <!-- U+2191: "↑" UPWARDS ARROW
+         U+2193: "↓" DOWNWARDS ARROW
+         U+2190: "←" LEFTWARDS ARROW
+         U+2192: "→" RIGHTWARDS ARROW -->
+    <Key
+        latin:keyLabel="^"
+        latin:moreKeys="&#x2191;,&#x2193;,&#x2190;,&#x2192;" />
+    <Key
+        latin:keyLabel="_" />
+    <!-- U+2260: "≠" NOT EQUAL TO
+         U+2248: "≈" ALMOST EQUAL TO
+         U+221E: "∞" INFINITY -->
+    <Key
+        latin:keyLabel="="
+        latin:moreKeys="&#x2260;,&#x2248;,&#x221E;" />
+    <include
+        latin:keyboardLayout="@xml/keys_square_brackets" />
+</merge>
diff --git a/java/res/xml/rowkeys_symbols_shift3.xml b/java/res/xml/rowkeys_symbols_shift3.xml
new file mode 100644
index 0000000..f5db0fe
--- /dev/null
+++ b/java/res/xml/rowkeys_symbols_shift3.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keyStyle="backFromMoreSymbolKeyStyle"
+        latin:keyWidth="15%p"
+        latin:visualInsetsRight="1%p" />
+    <!-- U+2122: "™" TRADE MARK SIGN -->
+    <Key
+        latin:keyLabel="&#x2122;" />
+    <!-- U+00AE: "®" REGISTERED SIGN -->
+    <Key
+        latin:keyLabel="&#x00AE;" />
+    <!-- U+00A9: "©" COPYRIGHT SIGN -->
+    <Key
+        latin:keyLabel="&#x00A9;" />
+    <!-- U+00B6: "¶" PILCROW SIGN
+         U+00A7: "§" SECTION SIGN -->
+    <Key
+        latin:keyLabel="&#x00B6;"
+        latin:moreKeys="&#x00A7;" />
+    <Key
+        latin:keyLabel="\\" />
+    <include
+        latin:keyboardLayout="@xml/keys_less_greater" />
+    <Key
+        latin:keyStyle="deleteKeyStyle"
+        latin:keyWidth="fillRight"
+        latin:visualInsetsLeft="1%p" />
+</merge>
diff --git a/java/res/xml/rowkeys_thai1.xml b/java/res/xml/rowkeys_thai1.xml
new file mode 100644
index 0000000..943112a
--- /dev/null
+++ b/java/res/xml/rowkeys_thai1.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0E0E: "ฎ" THAI CHARACTER DO CHADA -->
+            <Key
+                latin:keyLabel="&#x0E0E;" />
+            <!-- U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO -->
+            <Key
+                latin:keyLabel="&#x0E11;" />
+            <!-- U+0E18: "ธ" THAI CHARACTER THO THONG -->
+            <Key
+                latin:keyLabel="&#x0E18;" />
+            <!-- U+0E13: "ณ" THAI CHARACTER NO NEN -->
+            <Key
+                latin:keyLabel="&#x0E13;" />
+            <!-- U+0E0D: "ญ" THAI CHARACTER YO YING -->
+            <Key
+                latin:keyLabel="&#x0E0D;" />
+            <!-- U+0E10: "ฐ" THAI CHARACTER THO THAN -->
+            <Key
+                latin:keyLabel="&#x0E10;" />
+            <!-- U+0E03: "ฃ" THAI CHARACTER KHO KHUAT -->
+            <Key
+                latin:keyLabel="&#x0E03;" />
+            <!-- U+0E05: "ฅ" THAI CHARACTER KHO KHON -->
+            <Key
+                latin:keyLabel="&#x0E05;" />
+            <!-- U+0E51: "๑" THAI DIGIT ONE
+                 U+0E52: "๒" THAI DIGIT TWO
+                 U+0E53: "๓" THAI DIGIT THREE
+                 U+0E54: "๔" THAI DIGIT FOUR
+                 U+0E55: "๕" THAI DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0E51;"
+                latin:moreKeys="!fixedColumnOrder!4,&#x0E52;,&#x0E53;,&#x0E54;,&#x0E55;" />
+            <!-- U+0E56: "๖" THAI DIGIT SIX
+                 U+0E57: "๗" THAI DIGIT SEVEN
+                 U+0E58: "๘" THAI DIGIT EIGHT
+                 U+0E59: "๙" THAI DIGIT NINE
+                 U+0E50: "๐" THAI DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0E56;"
+                latin:moreKeys="!fixedColumnOrder!4,&#x0E57;,&#x0E58;,&#x0E59;,&#x0E50;" />
+        </case>
+        <default>
+            <!-- U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO -->
+            <Key
+                latin:keyLabel="&#x0E20;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1,&#x0E51;" />
+            <!-- U+0E16: "ถ" THAI CHARACTER THO THUNG -->
+            <Key
+                latin:keyLabel="&#x0E16;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2,&#x0E52;" />
+            <!-- U+0E04: "ค" THAI CHARACTER KHO KHWAI -->
+            <Key
+                latin:keyLabel="&#x0E04;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3,&#x0E53;" />
+            <!-- U+0E15: "ต" THAI CHARACTER TO TAO -->
+            <Key
+                latin:keyLabel="&#x0E15;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4,&#x0E54;" />
+            <!-- U+0E08: "จ" THAI CHARACTER CHO CHAN -->
+            <Key
+                latin:keyLabel="&#x0E08;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5,&#x0E55;" />
+            <!-- U+0E02: "ข" THAI CHARACTER KHO KHAI -->
+            <Key
+                latin:keyLabel="&#x0E02;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6,&#x0E56;" />
+            <!-- U+0E0A: "ช" THAI CHARACTER CHO CHANG -->
+            <Key
+                latin:keyLabel="&#x0E0A;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7,&#x0E57;" />
+            <!-- U+0E23: "ร" THAI CHARACTER RO RUA
+                 U+0E25: "ล" THAI CHARACTER LO LING -->
+            <Key
+                latin:keyLabel="&#x0E23;"
+                latin:moreKeys="&#x0E25;"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8,&#x0E58;" />
+            <!-- U+0E19: "น" THAI CHARACTER NO NU -->
+            <Key
+                latin:keyLabel="&#x0E19;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9,&#x0E59;" />
+            <!-- U+0E22: "ย" THAI CHARACTER YO YAK -->
+            <Key
+                latin:keyLabel="&#x0E22;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0,&#x0E50;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_thai2.xml b/java/res/xml/rowkeys_thai2.xml
new file mode 100644
index 0000000..02ea6c5
--- /dev/null
+++ b/java/res/xml/rowkeys_thai2.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0E24: "ฤ" THAI CHARACTER RU -->
+            <Key
+                latin:keyLabel="&#x0E24;" />
+            <!-- U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG -->
+            <Key
+                latin:keyLabel="&#x0E06;" />
+            <!-- U+0E0F: "ฏ" THAI CHARACTER TO PATAK -->
+            <Key
+                latin:keyLabel="&#x0E0F;" />
+            <!-- U+0E0C: "ฌ" THAI CHARACTER CHO CHOE -->
+            <Key
+                latin:keyLabel="&#x0E0C;" />
+            <!-- U+0E29: "ษ" THAI CHARACTER SO RUSI -->
+            <Key
+                latin:keyLabel="&#x0E29;" />
+            <!-- U+0E28: "ศ" THAI CHARACTER SO SALA -->
+            <Key
+                latin:keyLabel="&#x0E28;" />
+            <!-- U+0E0B: "ซ" THAI CHARACTER SO SO -->
+            <Key
+                latin:keyLabel="&#x0E0B;" />
+            <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
+                 U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO -->
+            <Key
+                latin:keyLabel="&#x0E3F;"
+                latin:moreKeys="&#x0E45;" />
+            <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK
+                 U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI -->
+            <Key
+                latin:keyLabel="&#x0E46;"
+                latin:moreKeys="&#x0E2F;" />
+        </case>
+        <default>
+            <!-- U+0E1F: "ฟ" THAI CHARACTER FO FAN
+                 U+0E1E: "พ" THAI CHARACTER PHO PHAN -->
+            <Key
+                latin:keyLabel="&#x0E1F;"
+                latin:moreKeys="&#x0E1E;" />
+            <!-- U+0E2B: "ห" THAI CHARACTER HO HIP -->
+            <Key
+                latin:keyLabel="&#x0E2B;" />
+            <!-- U+0E01: "ก" THAI CHARACTER KO KAI -->
+            <Key
+                latin:keyLabel="&#x0E01;" />
+            <!-- U+0E14: "ด" THAI CHARACTER DO DEK -->
+            <Key
+                latin:keyLabel="&#x0E14;" />
+            <!-- U+0E2A: "ส" THAI CHARACTER SO SUA -->
+            <Key
+                latin:keyLabel="&#x0E2A;" />
+            <!-- U+0E27: "ว" THAI CHARACTER WO WAEN -->
+            <Key
+                latin:keyLabel="&#x0E27;" />
+            <!-- U+0E07: "ง" THAI CHARACTER NGO NGU -->
+            <Key
+                latin:keyLabel="&#x0E07;" />
+            <!-- U+0E30: "ะ" THAI CHARACTER SARA A
+                 U+0E32: "า" THAI CHARACTER SARA AA
+                 U+0E33: " ำ" THAI CHARACTER SARA AM
+                 U+0E40: "เ" THAI CHARACTER SARA E
+                 U+0E41: "แ" THAI CHARACTER SARA AE
+                 U+0E43: "ใ" THAI CHARACTER SARA AI MAIMUAN
+                 U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI
+                 U+0E42: "โ" THAI CHARACTER SARA O -->
+            <Key
+                latin:keyLabel="&#x0E30;"
+                latin:moreKeys="&#x0E32;,&#x0E33;,&#x0E40;,&#x0E41;,&#x0E43;,&#x0E44;,&#x0E42;" />
+            <!-- U+0E31: " ั" THAI CHARACTER MAI HAN-AKAT
+                 U+0E34: " ิ" THAI CHARACTER SARA I
+                 U+0E35: " ี" THAI CHARACTER SARA II
+                 U+0E36: " ึ" THAI CHARACTER SARA UE
+                 U+0E37: " ื" THAI CHARACTER SARA UEE
+                 U+0E38: " ุ" THAI CHARACTER SARA U
+                 U+0E39: " ู" THAI CHARACTER SARA UU -->
+            <Key
+                latin:keyLabel="&#x0E31;"
+                latin:moreKeys="&#x0E34;,&#x0E35;,&#x0E36;,&#x0E37;,&#x0E38;,&#x0E39;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_thai3.xml b/java/res/xml/rowkeys_thai3.xml
new file mode 100644
index 0000000..72c0def
--- /dev/null
+++ b/java/res/xml/rowkeys_thai3.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0E09: "ฉ" THAI CHARACTER CHO CHING -->
+            <Key
+                latin:keyLabel="&#x0E09;" />
+            <!-- U+0E2E: "ฮ" THAI CHARACTER HO NOKHUK -->
+            <Key
+                latin:keyLabel="&#x0E2E;" />
+            <!-- U+0E12: "ฒ" THAI CHARACTER THO PHUTHAO -->
+            <Key
+                latin:keyLabel="&#x0E12;" />
+            <!-- U+0E2C: "ฬ" THAI CHARACTER LO CHULA -->
+            <Key
+                latin:keyLabel="&#x0E2C;" />
+            <!-- U+0E26: "ฦ" THAI CHARACTER LU -->
+            <Key
+                latin:keyLabel="&#x0E26;" />
+            <!-- U+0E4C: " ์" THAI CHARACTER THANTHAKHAT
+                 U+0E4D: " ํ" THAI CHARACTER NIKHAHIT
+                 U+0E3A: " ฺ" THAI CHARACTER PHINTHU -->
+            <Key
+                latin:keyLabel="&#x0E4C;"
+                latin:moreKeys="&#x0E4D;,&#x0E3A;" />
+            <!-- U+0E47: " ็" THAI CHARACTER MAITAIKHU -->
+            <Key
+                latin:keyLabel="&#x0E47;" />
+        </case>
+        <default>
+            <!-- U+0E1C: "ผ" THAI CHARACTER PHO PHUNG -->
+            <Key
+                latin:keyLabel="&#x0E1C;" />
+            <!-- U+0E1B: "ป" THAI CHARACTER PO PLA
+                 U+0E1A: "บ" THAI CHARACTER BO BAIMAI -->
+            <Key
+                latin:keyLabel="&#x0E1B;"
+                latin:moreKeys="&#x0E1A;" />
+            <!-- U+0E2D: "อ" THAI CHARACTER O ANG -->
+            <Key
+                latin:keyLabel="&#x0E2D;" />
+            <!-- U+0E17: "ท" THAI CHARACTER THO THAHAN -->
+            <Key
+                latin:keyLabel="&#x0E17;" />
+            <!-- U+0E21: "ม" THAI CHARACTER MO MA -->
+            <Key
+                latin:keyLabel="&#x0E21;" />
+            <!-- U+0E1D: "ฝ" THAI CHARACTER FO FA -->
+            <Key
+                latin:keyLabel="&#x0E1D;" />
+            <!-- U+0E48: " ่" THAI CHARACTER MAI EK
+                 U+0E49: " ้" THAI CHARACTER MAI THO
+                 U+0E4A: " ๊" THAI CHARACTER MAI TRI
+                 U+0E4B: " ๋" THAI CHARACTER MAI CHATTAWA -->
+            <Key
+                latin:keyLabel="&#x0E48;"
+                latin:moreKeys="&#x0E49;,&#x0E4A;,&#x0E4B;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rows_arabic.xml b/java/res/xml/rows_arabic.xml
new file mode 100644
index 0000000..6449af2
--- /dev/null
+++ b/java/res/xml/rows_arabic.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic2" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic3"
+            latin:keyXPos="5.0%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight"
+            latin:visualInsetsLeft="1%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_azerty.xml
similarity index 64%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_azerty.xml
index c2b45e7..a52504c 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_azerty.xml
@@ -21,6 +21,20 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty2" />
+    </Row>
     <Row
         latin:keyWidth="10%p"
     >
@@ -28,27 +42,13 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_azerty3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillBoth"
+            latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/rows_bulgarian.xml b/java/res/xml/rows_bulgarian.xml
new file mode 100644
index 0000000..883c283
--- /dev/null
+++ b/java/res/xml/rows_bulgarian.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian1" />
+    </Row>
+    <Row
+            latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian2" />
+    </Row>
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.636%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/rows_east_slavic.xml b/java/res/xml/rows_east_slavic.xml
new file mode 100644
index 0000000..5d3d768
--- /dev/null
+++ b/java/res/xml/rows_east_slavic.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic1" />
+    </Row>
+    <Row
+            latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.711%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/rows_farsi.xml b/java/res/xml/rows_farsi.xml
new file mode 100644
index 0000000..cc0c526
--- /dev/null
+++ b/java/res/xml/rows_farsi.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi2" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi3"
+            latin:keyXPos="5.0%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight"
+            latin:visualInsetsLeft="1%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_georgian.xml
similarity index 63%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_georgian.xml
index c2b45e7..9bddfc7 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_georgian.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -21,6 +21,21 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian2"
+            latin:keyXPos="5%p" />
+    </Row>
     <Row
         latin:keyWidth="10%p"
     >
@@ -28,27 +43,13 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_georgian3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillBoth"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/rows_greek.xml b/java/res/xml/rows_greek.xml
new file mode 100644
index 0000000..ca6d240
--- /dev/null
+++ b/java/res/xml/rows_greek.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/key_greek_semicolon" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek2"
+            latin:keyXPos="5%p" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_greek3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight"
+            latin:visualInsetsLeft="1%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/rows_hebrew.xml b/java/res/xml/rows_hebrew.xml
new file mode 100644
index 0000000..2d513df
--- /dev/null
+++ b/java/res/xml/rows_hebrew.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew1"
+            latin:keyXPos="5%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight"
+            latin:visualInsetsLeft="1%p" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew2" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew3"
+            latin:keyXPos="5%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/rows_hindi.xml b/java/res/xml/rows_hindi.xml
new file mode 100644
index 0000000..5c631eb
--- /dev/null
+++ b/java/res/xml/rows_hindi.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi1" />
+    </Row>
+    <Row
+            latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.711%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hindi3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_nordic.xml
similarity index 61%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_nordic.xml
index c2b45e7..3068917 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_nordic.xml
@@ -21,34 +21,37 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic1" />
+    </Row>
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic2" />
+    </Row>
+    <Row
+        latin:keyWidth="9.2%p"
     >
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <Spacer
+            latin:keyWidth="2.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillBoth"
+            latin:keyXPos="-15%p"
+            latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/kbd_phone_shift.xml b/java/res/xml/rows_number.xml
similarity index 63%
copy from java/res/xml/kbd_phone_shift.xml
copy to java/res/xml/rows_number.xml
index eea823f..8da83be 100644
--- a/java/res/xml/kbd_phone_shift.xml
+++ b/java/res/xml/rows_number.xml
@@ -18,10 +18,24 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="26.67%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
-</Keyboard>
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_number" />
+    <switch>
+        <case
+            latin:passwordInput="true"
+        >
+            <include
+                latin:keyboardLayout="@xml/rows_number_password" />
+        </case>
+        <default>
+            <include
+                latin:keyboardLayout="@xml/rows_number_normal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rows_number_normal.xml b/java/res/xml/rows_number_normal.xml
new file mode 100644
index 0000000..6f9429c
--- /dev/null
+++ b/java/res/xml/rows_number_normal.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row>
+<Key
+            latin:keyLabel="1"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="2"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="3"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="-"
+            latin:keyStyle="numFunctionalKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyLabel="4"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="5"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="6"
+            latin:keyStyle="numKeyStyle" />
+        <switch>
+            <case
+                latin:mode="date"
+            >
+                <Key
+                    latin:keyLabel="."
+                    latin:keyStyle="numFunctionalKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </case>
+            <case
+                latin:mode="time|datetime"
+            >
+                <Key
+                    latin:keyLabel="."
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:moreKeys="@string/more_keys_for_am_pm"
+                    latin:keyStyle="numFunctionalKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </case>
+            <default>
+                <Key
+                    latin:keyLabel=","
+                    latin:keyStyle="numFunctionalKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
+    </Row>
+    <Row>
+        <Key
+            latin:keyLabel="7"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyLabel="8"
+            latin:keyStyle="numKeyStyle"/>
+        <Key
+            latin:keyLabel="9"
+            latin:keyStyle="numKeyStyle" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyStyle="numSpaceKeyStyle" />
+        <Key
+            latin:keyLabel="0"
+            latin:keyStyle="numKeyStyle" />
+        <switch>
+            <case
+                latin:mode="date"
+            >
+                <Key
+                    latin:keyLabel="/"
+                    latin:keyStyle="numKeyStyle" />
+            </case>
+            <case
+                latin:mode="time"
+            >
+                <Key
+                    latin:keyLabel=":"
+                    latin:keyStyle="numKeyStyle" />
+            </case>
+            <case
+                latin:mode="datetime"
+            >
+                <!-- U+002F: "/" SOLIDUS -->
+                <Key
+                    latin:code="0x002F"
+                    latin:keyLabel="/ :"
+                    latin:moreKeys="!embeddedMoreKey!,:"
+                    latin:keyStyle="numKeyStyle" />
+            </case>
+            <default>
+                <Key
+                    latin:keyLabel="."
+                    latin:keyStyle="numKeyStyle" />
+            </default>
+        </switch>
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+</merge>
diff --git a/java/res/xml/kbd_rows_phone.xml b/java/res/xml/rows_number_password.xml
similarity index 70%
copy from java/res/xml/kbd_rows_phone.xml
copy to java/res/xml/rows_number_password.xml
index 5500a60..e4272ed 100644
--- a/java/res/xml/kbd_rows_phone.xml
+++ b/java/res/xml/rows_number_password.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -21,10 +21,6 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
     <Row>
         <Key
             latin:keyStyle="num1KeyStyle" />
@@ -32,10 +28,7 @@
             latin:keyStyle="num2KeyStyle" />
         <Key
             latin:keyStyle="num3KeyStyle" />
-        <Key
-            latin:keyLabel="-"
-            latin:keyStyle="numFunctionalKeyStyle"
-            latin:keyWidth="fillRight" />
+        <Spacer />
     </Row>
     <Row>
         <Key
@@ -44,10 +37,7 @@
             latin:keyStyle="num5KeyStyle" />
         <Key
             latin:keyStyle="num6KeyStyle" />
-        <Key
-            latin:keyLabel="."
-            latin:keyStyle="numFunctionalKeyStyle"
-            latin:keyWidth="fillRight" />
+        <Spacer />
     </Row>
     <Row>
         <Key
@@ -61,14 +51,12 @@
             latin:keyWidth="fillRight" />
     </Row>
     <Row>
-        <Key
-            latin:keyStyle="numSwitchToAltKeyStyle" />
+        <Spacer />
         <Key
             latin:keyStyle="num0KeyStyle" />
+        <Spacer />
         <Key
-            latin:keyStyle="numSpaceKeyStyle" />
-        <Key
-            latin:keyStyle="returnKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
 </merge>
diff --git a/java/res/xml/kbd_rows_phone.xml b/java/res/xml/rows_phone.xml
similarity index 82%
rename from java/res/xml/kbd_rows_phone.xml
rename to java/res/xml/rows_phone.xml
index 5500a60..630b24e 100644
--- a/java/res/xml/kbd_rows_phone.xml
+++ b/java/res/xml/rows_phone.xml
@@ -22,9 +22,9 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
+        latin:keyboardLayout="@xml/key_styles_common" />
     <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
+        latin:keyboardLayout="@xml/key_styles_number" />
     <Row>
         <Key
             latin:keyStyle="num1KeyStyle" />
@@ -62,13 +62,17 @@
     </Row>
     <Row>
         <Key
-            latin:keyStyle="numSwitchToAltKeyStyle" />
+            latin:keyStyle="numPhoneToSymbolKeyStyle" />
+        <!-- U+0030: "0" DIGIT ZERO -->
         <Key
-            latin:keyStyle="num0KeyStyle" />
+            latin:keyStyle="num0KeyStyle"
+            latin:code="0x0030"
+            latin:keyLabel="0 +"
+            latin:moreKeys="!embeddedMoreKey!,+" />
         <Key
             latin:keyStyle="numSpaceKeyStyle" />
         <Key
-            latin:keyStyle="returnKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
 </merge>
diff --git a/java/res/xml/kbd_rows_phone_shift.xml b/java/res/xml/rows_phone_symbols.xml
similarity index 79%
rename from java/res/xml/kbd_rows_phone_shift.xml
rename to java/res/xml/rows_phone_symbols.xml
index 3c283d3..7841c56 100644
--- a/java/res/xml/kbd_rows_phone_shift.xml
+++ b/java/res/xml/rows_phone_symbols.xml
@@ -22,9 +22,9 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
+        latin:keyboardLayout="@xml/key_styles_common" />
     <include
-        latin:keyboardLayout="@xml/kbd_numkey_styles" />
+        latin:keyboardLayout="@xml/key_styles_number" />
     <Row>
         <Key
             latin:keyLabel="("
@@ -42,13 +42,12 @@
     </Row>
     <Row>
         <Key
-            latin:keyLabel="N" />
+            latin:keyLabel="N"
+            latin:keyStyle="numKeyBaseStyle" />
         <!-- Pause is a comma. Check PhoneNumberUtils.java to see if this
             has changed. -->
         <Key
-            latin:code="44"
-            latin:keyLabel="@string/label_pause_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale" />
+            latin:keyStyle="numPauseKeyStyle" />
         <Key
             latin:keyLabel=","
             latin:keyStyle="numKeyStyle" />
@@ -62,9 +61,7 @@
             latin:keyStyle="numStarKeyStyle" />
         <!-- Wait is a semicolon. -->
         <Key
-            latin:code="59"
-            latin:keyLabel="@string/label_wait_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale" />
+            latin:keyStyle="numWaitKeyStyle" />
         <Key
             latin:keyLabel="#"
             latin:keyStyle="numKeyStyle" />
@@ -74,14 +71,14 @@
     </Row>
     <Row>
         <Key
-            latin:keyStyle="numSwitchToNumericKeyStyle" />
+            latin:keyStyle="numPhoneToNumericKeyStyle" />
         <Key
             latin:keyLabel="+"
             latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyStyle="numSpaceKeyStyle" />
         <Key
-            latin:keyStyle="returnKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
 </merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_qwerty.xml
similarity index 66%
rename from java/res/xml/kbd_qwerty_row3.xml
rename to java/res/xml/rows_qwerty.xml
index c2b45e7..716d106 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_qwerty.xml
@@ -21,6 +21,21 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty2"
+            latin:keyXPos="5%p" />
+    </Row>
     <Row
         latin:keyWidth="10%p"
     >
@@ -28,27 +43,13 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillBoth"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_qwertz.xml
similarity index 64%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_qwertz.xml
index c2b45e7..31a147c 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_qwertz.xml
@@ -21,6 +21,21 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty2"
+            latin:keyXPos="5%p" />
+    </Row>
     <Row
         latin:keyWidth="10%p"
     >
@@ -28,27 +43,13 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillBoth"
+            latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
+   <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/rows_south_slavic.xml b/java/res/xml/rows_south_slavic.xml
new file mode 100644
index 0000000..0544284
--- /dev/null
+++ b/java/res/xml/rows_south_slavic.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic1" />
+    </Row>
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.711%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_spanish.xml
similarity index 63%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_spanish.xml
index c2b45e7..b311297 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_spanish.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -21,6 +21,20 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_spanish2" />
+    </Row>
     <Row
         latin:keyWidth="10%p"
     >
@@ -28,27 +42,13 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillBoth"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/rows_symbols.xml b/java/res/xml/rows_symbols.xml
new file mode 100644
index 0000000..dcd8d59
--- /dev/null
+++ b/java/res/xml/rows_symbols.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols3" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols4" />
+</merge>
diff --git a/java/res/xml/rows_symbols4.xml b/java/res/xml/rows_symbols4.xml
new file mode 100644
index 0000000..3e26650
--- /dev/null
+++ b/java/res/xml/rows_symbols4.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="15%p" />
+        <switch>
+            <case
+                latin:hasShortcutKey="true"
+            >
+                <Key
+                    latin:keyStyle="shortcutKeyStyle" />
+            </case>
+            <!-- latin:hasShortcutKey="false" -->
+            <default>
+                <Key
+                    latin:keyLabel="@string/keylabel_for_comma"
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:additionalMoreKeys="@string/more_keys_for_comma"
+                    latin:keyStyle="f1MoreKeysStyle" />
+            </default>
+        </switch>
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyWidth="50%p" />
+        <Key
+            latin:keyStyle="punctuationKeyStyle" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+</merge>
diff --git a/java/res/xml/rows_symbols_shift.xml b/java/res/xml/rows_symbols_shift.xml
new file mode 100644
index 0000000..6205eed
--- /dev/null
+++ b/java/res/xml/rows_symbols_shift.xml
@@ -0,0 +1,48 @@
+<?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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/rows_symbols_shift4" />
+</merge>
diff --git a/java/res/xml/rows_symbols_shift4.xml b/java/res/xml/rows_symbols_shift4.xml
new file mode 100644
index 0000000..28b6ab8
--- /dev/null
+++ b/java/res/xml/rows_symbols_shift4.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="15%p" />
+        <!-- U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+        <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
+        <!-- latin:keyLabelFlags="hasPopupHint" -->
+        <!-- latin:moreKeys="&#x201F;" -->
+        <!-- U+201E: "„" DOUBLE LOW-9 QUOTATION MARK -->
+        <Key
+            latin:keyLabel="&#x201E;"
+            latin:backgroundType="functional" />
+        <Key
+            latin:keyStyle="spaceKeyStyle"
+            latin:keyWidth="50%p" />
+        <!-- U+2026: "…" HORIZONTAL ELLIPSIS -->
+        <Key
+            latin:keyLabel="&#x2026;"
+            latin:backgroundType="functional" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+</merge>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/rows_thai.xml
similarity index 61%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_thai.xml
index c2b45e7..6b80df6 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_thai.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -21,6 +21,21 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai1" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai2"
+            latin:keyXPos="5%p" />
+    </Row>
     <Row
         latin:keyWidth="10%p"
     >
@@ -28,27 +43,13 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
-        <Key
-            latin:keyLabel="z"
-            latin:moreKeys="@string/more_keys_for_z" />
-        <Key
-            latin:keyLabel="x" />
-        <Key
-            latin:keyLabel="c"
-            latin:moreKeys="@string/more_keys_for_c" />
-        <Key
-            latin:keyLabel="v"
-            latin:moreKeys="@string/more_keys_for_v" />
-        <Key
-            latin:keyLabel="b" />
-        <Key
-            latin:keyLabel="n"
-            latin:moreKeys="@string/more_keys_for_n" />
-        <Key
-            latin:keyLabel="m" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_thai3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillBoth"
+            latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/spell_checker_settings.xml b/java/res/xml/spell_checker_settings.xml
index f402555..222b98b 100644
--- a/java/res/xml/spell_checker_settings.xml
+++ b/java/res/xml/spell_checker_settings.xml
@@ -18,9 +18,9 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:title="@string/android_spell_checker_settings">
   <CheckBoxPreference
-     android:key="use_proximity"
-     android:title="@string/use_proximity_option_title"
-     android:summary="@string/use_proximity_option_summary"
+     android:key="pref_spellcheck_use_contacts"
+     android:title="@string/use_contacts_for_spellchecking_option_title"
+     android:summary="@string/use_contacts_for_spellchecking_option_summary"
      android:persistent="true"
      android:defaultValue="true" />
 </PreferenceScreen>
diff --git a/java/res/xml/spellchecker.xml b/java/res/xml/spellchecker.xml
index 30fac5b..2e4448c 100644
--- a/java/res/xml/spellchecker.xml
+++ b/java/res/xml/spellchecker.xml
@@ -17,11 +17,12 @@
  */
 -->
 
-<!-- The attributes in this XML file provide the configuration information -->
-<!-- for the spell checker -->
+<!-- The attributes in this XML file provide the configuration information
+     for the spell checker -->
 
 <spell-checker xmlns:android="http://schemas.android.com/apk/res/android"
-        android:label="@string/spell_checker_service_name">
+        android:label="@string/aosp_spell_checker_service_name"
+        android:settingsActivity="com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsActivity">
     <subtype
             android:label="@string/subtype_generic"
             android:subtypeLocale="en"
@@ -42,4 +43,16 @@
             android:label="@string/subtype_generic"
             android:subtypeLocale="es"
     />
+    <subtype
+            android:label="@string/subtype_generic"
+            android:subtypeLocale="ru"
+    />
+    <subtype
+            android:label="@string/subtype_generic"
+            android:subtypeLocale="cs"
+    />
+    <subtype
+            android:label="@string/subtype_generic"
+            android:subtypeLocale="nl"
+    />
 </spell-checker>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
new file mode 100644
index 0000000..dd43166
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardView;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
+ * {@link AccessibilityEvent}s for individual {@link Key}s.
+ * <p>
+ * A virtual sub-tree is composed of imaginary {@link View}s that are reported
+ * as a part of the view hierarchy for accessibility purposes. This enables
+ * custom views that draw complex content to report them selves as a tree of
+ * virtual views, thus conveying their logical structure.
+ * </p>
+ */
+public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
+    private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
+
+    private final KeyboardView mKeyboardView;
+    private final InputMethodService mInputMethodService;
+    private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
+    private final AccessibilityUtils mAccessibilityUtils;
+
+    /** A map of integer IDs to {@link Key}s. */
+    private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
+
+    /** Temporary rect used to calculate in-screen bounds. */
+    private final Rect mTempBoundsInScreen = new Rect();
+
+    /** The parent view's cached on-screen location. */
+    private final int[] mParentLocation = new int[2];
+
+    public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) {
+        mKeyboardView = keyboardView;
+        mInputMethodService = inputMethod;
+
+        mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
+        mAccessibilityUtils = AccessibilityUtils.getInstance();
+
+        assignVirtualViewIds();
+        updateParentLocation();
+
+        // Ensure that the on-screen bounds are cleared when the layout changes.
+        mKeyboardView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
+    }
+
+    /**
+     * Creates and populates an {@link AccessibilityEvent} for the specified key
+     * and event type.
+     *
+     * @param key A key on the host keyboard view.
+     * @param eventType The event type to create.
+     * @return A populated {@link AccessibilityEvent} for the key.
+     * @see AccessibilityEvent
+     */
+    public AccessibilityEvent createAccessibilityEvent(Key key, int eventType) {
+        final int virtualViewId = generateVirtualViewIdForKey(key);
+        final String keyDescription = getKeyDescription(key);
+
+        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+        event.setPackageName(mKeyboardView.getContext().getPackageName());
+        event.setClassName(key.getClass().getName());
+        event.getText().add(keyDescription);
+
+        final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event);
+        record.setSource(mKeyboardView, virtualViewId);
+
+        return event;
+    }
+
+    /**
+     * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual
+     * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or
+     * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of
+     * the view hierarchy for accessibility purposes. This enables custom views
+     * that draw complex content to report them selves as a tree of virtual
+     * views, thus conveying their logical structure.
+     * </p>
+     * <p>
+     * The implementer is responsible for obtaining an accessibility node info
+     * from the pool of reusable instances and setting the desired properties of
+     * the node info before returning it.
+     * </p>
+     *
+     * @param virtualViewId A client defined virtual view id.
+     * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual
+     *         descendant or the host View.
+     * @see AccessibilityNodeInfoCompat
+     */
+    @Override
+    public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
+        AccessibilityNodeInfoCompat info = null;
+
+        if (virtualViewId == View.NO_ID) {
+            // We are requested to create an AccessibilityNodeInfo describing
+            // this View, i.e. the root of the virtual sub-tree.
+            info = AccessibilityNodeInfoCompat.obtain(mKeyboardView);
+            ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info);
+
+            // Add the virtual children of the root View.
+            // TODO: Need to assign a unique ID to each key.
+            final Keyboard keyboard = mKeyboardView.getKeyboard();
+            final Key[] keys = keyboard.mKeys;
+            for (Key key : keys) {
+                final int childVirtualViewId = generateVirtualViewIdForKey(key);
+                info.addChild(mKeyboardView, childVirtualViewId);
+            }
+        } else {
+            // Find the view that corresponds to the given id.
+            final Key key = mVirtualViewIdToKey.get(virtualViewId);
+            if (key == null) {
+                Log.e(TAG, "Invalid virtual view ID: " + virtualViewId);
+                return null;
+            }
+
+            final String keyDescription = getKeyDescription(key);
+            final Rect boundsInParent = key.mHitBox;
+
+            // Calculate the key's in-screen bounds.
+            mTempBoundsInScreen.set(boundsInParent);
+            mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]);
+
+            final Rect boundsInScreen = mTempBoundsInScreen;
+
+            // Obtain and initialize an AccessibilityNodeInfo with
+            // information about the virtual view.
+            info = AccessibilityNodeInfoCompat.obtain();
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT);
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION);
+            info.setPackageName(mKeyboardView.getContext().getPackageName());
+            info.setClassName(key.getClass().getName());
+            info.setBoundsInParent(boundsInParent);
+            info.setBoundsInScreen(boundsInScreen);
+            info.setParent(mKeyboardView);
+            info.setSource(mKeyboardView, virtualViewId);
+            info.setBoundsInScreen(boundsInScreen);
+            info.setText(keyDescription);
+        }
+
+        return info;
+    }
+
+    /**
+     * Performs an accessibility action on a virtual view, i.e. a descendant of
+     * the host View, with the given <code>virtualViewId</code> or the host View itself if
+     * <code>virtualViewId</code> equals to {@link View#NO_ID}.
+     *
+     * @param action The action to perform.
+     * @param virtualViewId A client defined virtual view id.
+     * @return True if the action was performed.
+     * @see #createAccessibilityNodeInfo(int)
+     * @see AccessibilityNodeInfoCompat
+     */
+    @Override
+    public boolean performAccessibilityAction(int action, int virtualViewId) {
+        if (virtualViewId == View.NO_ID) {
+            // Perform the action on the host View.
+            switch (action) {
+            case AccessibilityNodeInfoCompat.ACTION_SELECT:
+                if (!mKeyboardView.isSelected()) {
+                    mKeyboardView.setSelected(true);
+                    return mKeyboardView.isSelected();
+                }
+                break;
+            case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
+                if (mKeyboardView.isSelected()) {
+                    mKeyboardView.setSelected(false);
+                    return !mKeyboardView.isSelected();
+                }
+                break;
+            }
+        } else {
+            // Find the view that corresponds to the given id.
+            final Key child = mVirtualViewIdToKey.get(virtualViewId);
+            if (child == null)
+                return false;
+
+            // Perform the action on a virtual view.
+            switch (action) {
+            case AccessibilityNodeInfoCompat.ACTION_SELECT:
+                // TODO: Provide some focus indicator.
+                return true;
+            case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
+                // TODO: Provide some clear focus indicator.
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Finds {@link AccessibilityNodeInfoCompat}s by text. The match is case
+     * insensitive containment. The search is relative to the virtual view, i.e.
+     * a descendant of the host View, with the given <code>virtualViewId</code> or the host
+     * View itself <code>virtualViewId</code> equals to {@link View#NO_ID}.
+     *
+     * @param virtualViewId A client defined virtual view id which defined the
+     *            root of the tree in which to perform the search.
+     * @param text The searched text.
+     * @return A list of node info.
+     * @see #createAccessibilityNodeInfo(int)
+     * @see AccessibilityNodeInfoCompat
+     */
+    @Override
+    public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(
+            String text, int virtualViewId) {
+        final String searchedLowerCase = text.toLowerCase();
+        final Keyboard keyboard = mKeyboardView.getKeyboard();
+
+        List<AccessibilityNodeInfoCompat> results = null;
+
+        if (virtualViewId == View.NO_ID) {
+            for (Key key : keyboard.mKeys) {
+                results = findByTextAndPopulate(searchedLowerCase, key, results);
+            }
+        } else {
+            final Key key = mVirtualViewIdToKey.get(virtualViewId);
+
+            results = findByTextAndPopulate(searchedLowerCase, key, results);
+        }
+
+        if (results == null) {
+            return Collections.emptyList();
+        }
+
+        return results;
+    }
+
+    /**
+     * Helper method for {@link #findAccessibilityNodeInfosByText(String, int)}.
+     * Takes a current set of results and matches a specified key against a
+     * lower-case search string. Returns an updated list of results.
+     *
+     * @param searchedLowerCase The lower-case search string.
+     * @param key The key to compare against.
+     * @param results The current list of results, or {@code null} if no results
+     *            found.
+     * @return An updated list of results, or {@code null} if no results found.
+     */
+    private List<AccessibilityNodeInfoCompat> findByTextAndPopulate(String searchedLowerCase,
+            Key key, List<AccessibilityNodeInfoCompat> results) {
+        if (!keyContainsText(key, searchedLowerCase)) {
+            return results;
+        }
+
+        final int childVirtualViewId = generateVirtualViewIdForKey(key);
+        final AccessibilityNodeInfoCompat nodeInfo = createAccessibilityNodeInfo(
+                childVirtualViewId);
+
+        if (results == null) {
+            results = new LinkedList<AccessibilityNodeInfoCompat>();
+        }
+
+        results.add(nodeInfo);
+
+        return results;
+    }
+
+    /**
+     * Returns whether a key's current description contains the lower-case
+     * search text.
+     *
+     * @param key The key to compare against.
+     * @param textLowerCase The lower-case search string.
+     * @return {@code true} if the key contains the search text.
+     */
+    private boolean keyContainsText(Key key, String textLowerCase) {
+        if (key == null) {
+            return false;
+        }
+
+        final String description = getKeyDescription(key);
+
+        if (description == null) {
+            return false;
+        }
+
+        return description.toLowerCase().contains(textLowerCase);
+    }
+
+    /**
+     * Returns the context-specific description for a {@link Key}.
+     *
+     * @param key The key to describe.
+     * @return The context-specific description of the key.
+     */
+    private String getKeyDescription(Key key) {
+        final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
+        final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo);
+        final String keyDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
+                mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure);
+
+        return keyDescription;
+    }
+
+    /**
+     * Assigns virtual view IDs to keyboard keys and populates the related maps.
+     */
+    private void assignVirtualViewIds() {
+        final Keyboard keyboard = mKeyboardView.getKeyboard();
+        if (keyboard == null) {
+            return;
+        }
+
+        mVirtualViewIdToKey.clear();
+
+        final Key[] keys = keyboard.mKeys;
+        for (Key key : keys) {
+            final int virtualViewId = generateVirtualViewIdForKey(key);
+            mVirtualViewIdToKey.put(virtualViewId, key);
+        }
+    }
+
+    /**
+     * Updates the parent's on-screen location.
+     */
+    private void updateParentLocation() {
+        mKeyboardView.getLocationOnScreen(mParentLocation);
+    }
+
+    /**
+     * Generates a virtual view identifier for the specified key.
+     *
+     * @param key The key to identify.
+     * @return A virtual view identifier.
+     */
+    private static int generateVirtualViewIdForKey(Key key) {
+        // The key code is unique within an instance of a Keyboard.
+        return key.mCode;
+    }
+
+    private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+        @Override
+        public void onGlobalLayout() {
+            assignVirtualViewIds();
+            updateParentLocation();
+        }
+    };
+}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 4666332..667b109 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.os.SystemClock;
@@ -28,11 +27,9 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.compat.AccessibilityManagerCompatWrapper;
 import com.android.inputmethod.compat.AudioManagerCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.compat.SettingsSecureCompatUtils;
+import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.R;
 
 public class AccessibilityUtils {
@@ -45,7 +42,6 @@
 
     private Context mContext;
     private AccessibilityManager mAccessibilityManager;
-    private AccessibilityManagerCompatWrapper mCompatManager;
     private AudioManagerCompatWrapper mAudioManager;
 
     /*
@@ -55,15 +51,15 @@
      */
     private static final boolean ENABLE_ACCESSIBILITY = true;
 
-    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
+    public static void init(InputMethodService inputMethod) {
         if (!ENABLE_ACCESSIBILITY)
             return;
 
         // These only need to be initialized if the kill switch is off.
-        sInstance.initInternal(inputMethod, prefs);
-        KeyCodeDescriptionMapper.init(inputMethod, prefs);
-        AccessibleInputMethodServiceProxy.init(inputMethod, prefs);
-        AccessibleKeyboardViewProxy.init(inputMethod, prefs);
+        sInstance.initInternal(inputMethod);
+        KeyCodeDescriptionMapper.init();
+        AccessibleInputMethodServiceProxy.init(inputMethod);
+        AccessibleKeyboardViewProxy.init(inputMethod);
     }
 
     public static AccessibilityUtils getInstance() {
@@ -74,11 +70,10 @@
         // This class is not publicly instantiable.
     }
 
-    private void initInternal(Context context, SharedPreferences prefs) {
+    private void initInternal(Context context) {
         mContext = context;
         mAccessibilityManager = (AccessibilityManager) context
                 .getSystemService(Context.ACCESSIBILITY_SERVICE);
-        mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager);
 
         final AudioManager audioManager = (AudioManager) context
                 .getSystemService(Context.AUDIO_SERVICE);
@@ -95,7 +90,7 @@
     public boolean isTouchExplorationEnabled() {
         return ENABLE_ACCESSIBILITY
                 && mAccessibilityManager.isEnabled()
-                && mCompatManager.isTouchExplorationEnabled();
+                && mAccessibilityManager.isTouchExplorationEnabled();
     }
 
     /**
@@ -109,19 +104,19 @@
     public boolean isTouchExplorationEvent(MotionEvent event) {
         final int action = event.getAction();
 
-        return action == MotionEventCompatUtils.ACTION_HOVER_ENTER
-                || action == MotionEventCompatUtils.ACTION_HOVER_EXIT
-                || action == MotionEventCompatUtils.ACTION_HOVER_MOVE;
+        return action == MotionEvent.ACTION_HOVER_ENTER
+                || action == MotionEvent.ACTION_HOVER_EXIT
+                || action == MotionEvent.ACTION_HOVER_MOVE;
     }
 
     /**
      * Returns whether the device should obscure typed password characters.
      * Typically this means speaking "dot" in place of non-control characters.
-     * 
+     *
      * @return {@code true} if the device should obscure password characters.
      */
-    public boolean shouldObscureInput(EditorInfo attribute) {
-        if (attribute == null)
+    public boolean shouldObscureInput(EditorInfo editorInfo) {
+        if (editorInfo == null)
             return false;
 
         // The user can optionally force speaking passwords.
@@ -137,7 +132,7 @@
             return false;
 
         // Don't speak if the IME is connected to a password field.
-        return InputTypeCompatUtils.isPasswordInputType(attribute.inputType);
+        return InputTypeUtils.isPasswordInputType(editorInfo.inputType);
     }
 
     /**
@@ -171,11 +166,11 @@
      * Handles speaking the "connect a headset to hear passwords" notification
      * when connecting to a password field.
      *
-     * @param attribute The input connection's editor info attribute.
+     * @param editorInfo The input connection's editor info attribute.
      * @param restarting Whether the connection is being restarted.
      */
-    public void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
-        if (shouldObscureInput(attribute)) {
+    public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+        if (shouldObscureInput(editorInfo)) {
             final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
             speak(text);
         }
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
index 4ab9cb8..961176b 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
@@ -17,31 +17,15 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Vibrator;
-import android.text.TextUtils;
 import android.view.KeyEvent;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
 public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActionListener {
     private static final AccessibleInputMethodServiceProxy sInstance =
             new AccessibleInputMethodServiceProxy();
 
-    /*
-     * Delay for the handler event that's fired when Accessibility is on and the
-     * user hovers outside of any valid keys. This is used to let the user know
-     * that if they lift their finger, nothing will be typed.
-     */
-    private static final long DELAY_NO_HOVER_SELECTION = 250;
-
     /**
      * Duration of the key click vibration in milliseconds.
      */
@@ -52,38 +36,9 @@
     private InputMethodService mInputMethod;
     private Vibrator mVibrator;
     private AudioManager mAudioManager;
-    private AccessibilityHandler mAccessibilityHandler;
 
-    private static class AccessibilityHandler
-            extends StaticInnerHandlerWrapper<AccessibleInputMethodServiceProxy> {
-        private static final int MSG_NO_HOVER_SELECTION = 0;
-
-        public AccessibilityHandler(AccessibleInputMethodServiceProxy outerInstance,
-                Looper looper) {
-            super(outerInstance, looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-            case MSG_NO_HOVER_SELECTION:
-                getOuterInstance().notifyNoHoverSelection();
-                break;
-            }
-        }
-
-        public void postNoHoverSelection() {
-            removeMessages(MSG_NO_HOVER_SELECTION);
-            sendEmptyMessageDelayed(MSG_NO_HOVER_SELECTION, DELAY_NO_HOVER_SELECTION);
-        }
-
-        public void cancelNoHoverSelection() {
-            removeMessages(MSG_NO_HOVER_SELECTION);
-        }
-    }
-
-    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
-        sInstance.initInternal(inputMethod, prefs);
+    public static void init(InputMethodService inputMethod) {
+        sInstance.initInternal(inputMethod);
     }
 
     public static AccessibleInputMethodServiceProxy getInstance() {
@@ -94,30 +49,10 @@
         // Not publicly instantiable.
     }
 
-    private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
+    private void initInternal(InputMethodService inputMethod) {
         mInputMethod = inputMethod;
         mVibrator = (Vibrator) inputMethod.getSystemService(Context.VIBRATOR_SERVICE);
         mAudioManager = (AudioManager) inputMethod.getSystemService(Context.AUDIO_SERVICE);
-        mAccessibilityHandler = new AccessibilityHandler(this, inputMethod.getMainLooper());
-    }
-
-    /**
-     * If touch exploration is enabled, cancels the event sent by
-     * {@link AccessibleInputMethodServiceProxy#onHoverExit(int)} because the
-     * user is currently hovering above a key.
-     */
-    @Override
-    public void onHoverEnter(int primaryCode) {
-        mAccessibilityHandler.cancelNoHoverSelection();
-    }
-
-    /**
-     * If touch exploration is enabled, sends a delayed event to notify the user
-     * that they are not currently hovering above a key.
-     */
-    @Override
-    public void onHoverExit(int primaryCode) {
-        mAccessibilityHandler.postNoHoverSelection();
     }
 
     /**
@@ -125,8 +60,6 @@
      */
     @Override
     public void onFlickGesture(int direction) {
-        final int keyEventCode;
-
         switch (direction) {
         case FlickGestureDetector.FLICK_LEFT:
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
@@ -148,27 +81,4 @@
         mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, FX_VOLUME);
         mInputMethod.sendDownUpKeyEvents(keyCode);
     }
-
-    /**
-     * When Accessibility is turned on, notifies the user that they are not
-     * currently hovering above a key. By default this will speak the currently
-     * entered text.
-     */
-    private void notifyNoHoverSelection() {
-        final ExtractedText extracted = mInputMethod.getCurrentInputConnection().getExtractedText(
-                new ExtractedTextRequest(), 0);
-
-        if (extracted == null)
-            return;
-
-        final CharSequence text;
-
-        if (TextUtils.isEmpty(extracted.text)) {
-            text = mInputMethod.getString(R.string.spoken_no_text_entered);
-        } else {
-            text = mInputMethod.getString(R.string.spoken_current_text_is, extracted.text);
-        }
-
-        AccessibilityUtils.getInstance().speak(text);
-    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
index c1e92be..31d17d0 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
@@ -18,24 +18,6 @@
 
 public interface AccessibleKeyboardActionListener {
     /**
-     * Called when the user hovers inside a key. This is sent only when
-     * Accessibility is turned on. For keys that repeat, this is only called
-     * once.
-     *
-     * @param primaryCode the code of the key that was hovered over
-     */
-    public void onHoverEnter(int primaryCode);
-
-    /**
-     * Called when the user hovers outside a key. This is sent only when
-     * Accessibility is turned on. For keys that repeat, this is only called
-     * once.
-     *
-     * @param primaryCode the code of the key that was hovered over
-     */
-    public void onHoverExit(int primaryCode);
-
-    /**
      * @param direction the direction of the flick gesture, one of
      *            <ul>
      *              <li>{@link FlickGestureDetector#FLICK_UP}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index cef8226..c85a551 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -17,35 +17,36 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.inputmethodservice.InputMethodService;
-import android.util.Log;
+import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
-import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
 import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.R;
 
-public class AccessibleKeyboardViewProxy {
-    private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName();
+public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
 
     private InputMethodService mInputMethod;
     private FlickGestureDetector mGestureDetector;
     private LatinKeyboardView mView;
     private AccessibleKeyboardActionListener mListener;
+    private AccessibilityEntityProvider mAccessibilityNodeProvider;
 
-    private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
+    private Key mLastHoverKey = null;
 
-    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
-        sInstance.initInternal(inputMethod, prefs);
+    public static void init(InputMethodService inputMethod) {
+        sInstance.initInternal(inputMethod);
         sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
     }
 
@@ -53,15 +54,11 @@
         return sInstance;
     }
 
-    public static void setView(LatinKeyboardView view) {
-        sInstance.mView = view;
-    }
-
     private AccessibleKeyboardViewProxy() {
         // Not publicly instantiable.
     }
 
-    private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
+    private void initInternal(InputMethodService inputMethod) {
         final Paint paint = new Paint();
         paint.setTextAlign(Paint.Align.LEFT);
         paint.setTextSize(14.0f);
@@ -72,35 +69,39 @@
         mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
     }
 
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
-            PointerTracker tracker) {
-        if (mView == null) {
-            Log.e(TAG, "No keyboard view set!");
-            return false;
+    /**
+     * Sets the view wrapped by this proxy.
+     *
+     * @param view The view to wrap.
+     */
+    public void setView(LatinKeyboardView view) {
+        if (view == null) {
+            // Ignore null views.
+            return;
         }
 
-        switch (event.getEventType()) {
-        case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
-            final Key key = tracker.getKey(mLastHoverKeyIndex);
+        mView = view;
 
-            if (key == null)
-                break;
+        // Ensure that the view has an accessibility delegate.
+        ViewCompat.setAccessibilityDelegate(view, this);
+    }
 
-            final EditorInfo info = mInputMethod.getCurrentInputEditorInfo();
-            final boolean shouldObscure = AccessibilityUtils.getInstance().shouldObscureInput(info);
-            final CharSequence description = KeyCodeDescriptionMapper.getInstance()
-                    .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key,
-                            shouldObscure);
-
-            if (description == null)
-                return false;
-
-            event.getText().add(description);
-
-            break;
+    /**
+     * Proxy method for View.getAccessibilityNodeProvider(). This method is
+     * called in SDK version 15 and higher to obtain the virtual node hierarchy
+     * provider.
+     *
+     * @return The accessibility node provider for the current keyboard.
+     */
+    @Override
+    public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
+        // Instantiate the provide only when requested. Since the system
+        // will call this method multiple times it is a good practice to
+        // cache the provider instance.
+        if (mAccessibilityNodeProvider == null) {
+            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
         }
-
-        return true;
+        return mAccessibilityNodeProvider;
     }
 
     /**
@@ -123,53 +124,94 @@
      * @param event The touch exploration hover event.
      * @return {@code true} if the event was handled
      */
-    /*package*/ boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) {
+    /* package */boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) {
         final int x = (int) event.getX();
         final int y = (int) event.getY();
+        final Key key = tracker.getKeyOn(x, y);
+        final Key previousKey = mLastHoverKey;
+
+        mLastHoverKey = key;
 
         switch (event.getAction()) {
-        case MotionEventCompatUtils.ACTION_HOVER_ENTER:
-        case MotionEventCompatUtils.ACTION_HOVER_MOVE:
-            final int keyIndex = tracker.getKeyIndexOn(x, y);
-
-            if (keyIndex != mLastHoverKeyIndex) {
-                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
-                mLastHoverKeyIndex = keyIndex;
-                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
+        case MotionEvent.ACTION_HOVER_ENTER:
+        case MotionEvent.ACTION_HOVER_EXIT:
+            return onHoverKey(key, event);
+        case MotionEvent.ACTION_HOVER_MOVE:
+            if (key != previousKey) {
+                return onTransitionKey(key, previousKey, event);
+            } else {
+                return onHoverKey(key, event);
             }
-
-            return true;
         }
 
         return false;
     }
 
-    private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
-        if (mListener == null) {
-            Log.e(TAG, "No accessible keyboard action listener set!");
-            return;
+    /**
+     * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT
+     * on the previous key, a HOVER_ENTER on the current key, and a HOVER_MOVE
+     * on the current key.
+     *
+     * @param currentKey The currently hovered key.
+     * @param previousKey The previously hovered key.
+     * @param event The event that triggered the transition.
+     * @return {@code true} if the event was handled.
+     */
+    private boolean onTransitionKey(Key currentKey, Key previousKey, MotionEvent event) {
+        final int savedAction = event.getAction();
+
+        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+        onHoverKey(previousKey, event);
+
+        event.setAction(MotionEvent.ACTION_HOVER_ENTER);
+        onHoverKey(currentKey, event);
+
+        event.setAction(MotionEvent.ACTION_HOVER_MOVE);
+        final boolean handled = onHoverKey(currentKey, event);
+
+        event.setAction(savedAction);
+
+        return handled;
+    }
+
+    /**
+     * Handles a hover event on a key. If {@link Key} extended View, this would
+     * be analogous to calling View.onHoverEvent(MotionEvent).
+     *
+     * @param key The currently hovered key.
+     * @param event The hover event.
+     * @return {@code true} if the event was handled.
+     */
+    private boolean onHoverKey(Key key, MotionEvent event) {
+        // Null keys can't receive events.
+        if (key == null) {
+            return false;
         }
 
-        if (mView == null) {
-            Log.e(TAG, "No keyboard view set!");
-            return;
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_HOVER_ENTER:
+            sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+            break;
+        case MotionEvent.ACTION_HOVER_EXIT:
+            sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
+            break;
         }
 
-        if (keyIndex == KeyDetector.NOT_A_KEY)
-            return;
+        return true;
+    }
 
-        final Key key = tracker.getKey(keyIndex);
+    /**
+     * Populates and sends an {@link AccessibilityEvent} for the specified key.
+     *
+     * @param key The key to send an event for.
+     * @param eventType The type of event to send.
+     */
+    private void sendAccessibilityEventForKey(Key key, int eventType) {
+        final AccessibilityEntityProvider nodeProvider = getAccessibilityNodeProvider(null);
+        final AccessibilityEvent event = nodeProvider.createAccessibilityEvent(key, eventType);
 
-        if (key == null)
-            return;
-
-        if (entering) {
-            mListener.onHoverEnter(key.mCode);
-            mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER);
-        } else {
-            mListener.onHoverExit(key.mCode);
-            mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_EXIT);
-        }
+        // Propagates the event up the view hierarchy.
+        mView.getParent().requestSendAccessibilityEvent(mView, event);
     }
 
     private class KeyboardFlickGestureDetector extends FlickGestureDetector {
@@ -185,4 +227,71 @@
             return true;
         }
     }
+
+    /**
+     * Notifies the user of changes in the keyboard shift state.
+     */
+    public void notifyShiftState() {
+        final Keyboard keyboard = mView.getKeyboard();
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final Context context = mView.getContext();
+        final CharSequence text;
+
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            text = context.getText(R.string.spoken_description_shiftmode_locked);
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            text = context.getText(R.string.spoken_description_shiftmode_on);
+            break;
+        default:
+            text = context.getText(R.string.spoken_description_shiftmode_off);
+        }
+
+        AccessibilityUtils.getInstance().speak(text);
+    }
+
+    /**
+     * Notifies the user of changes in the keyboard symbols state.
+     */
+    public void notifySymbolsState() {
+        final Keyboard keyboard = mView.getKeyboard();
+        final Context context = mView.getContext();
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final int resId;
+
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET:
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_mode_alpha;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_mode_symbol;
+            break;
+        case KeyboardId.ELEMENT_PHONE:
+            resId = R.string.spoken_description_mode_phone;
+            break;
+        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
+            resId = R.string.spoken_description_mode_phone_shift;
+            break;
+        default:
+            resId = -1;
+        }
+
+        if (resId < 0) {
+            return;
+        }
+
+        final String text = context.getString(resId);
+        AccessibilityUtils.getInstance().speak(text);
+    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
index 9d99e31..e8ec376 100644
--- a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
+++ b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
@@ -21,7 +21,6 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
-import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
@@ -31,10 +30,10 @@
  * A flick gesture is defined as a stream of hover events with the following
  * properties:
  * <ul>
- *   <li>Begins with a {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event
- *   <li>Contains any number of {@link MotionEventCompatUtils#ACTION_HOVER_MOVE}
+ *   <li>Begins with a {@link MotionEvent#ACTION_HOVER_ENTER} event
+ *   <li>Contains any number of {@link MotionEvent#ACTION_HOVER_MOVE}
  *       events
- *   <li>Ends with a {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event
+ *   <li>Ends with a {@link MotionEvent#ACTION_HOVER_EXIT} event
  *   <li>Maximum duration of 250 milliseconds
  *   <li>Minimum distance between enter and exit points must be at least equal to
  *       scaled double tap slop (see
@@ -112,7 +111,7 @@
     public boolean onHoverEvent(MotionEvent event, AccessibleKeyboardViewProxy view,
             PointerTracker tracker) {
         // Always cache and consume the first hover event.
-        if (event.getAction() == MotionEventCompatUtils.ACTION_HOVER_ENTER) {
+        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
             mCachedView = view;
             mCachedTracker = tracker;
             mCachedHoverEnter = MotionEvent.obtain(event);
@@ -126,13 +125,12 @@
         }
 
         final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event);
-        final long timeout = event.getEventTime() - mCachedHoverEnter.getEventTime();
 
         switch (event.getAction()) {
-        case MotionEventCompatUtils.ACTION_HOVER_MOVE:
+        case MotionEvent.ACTION_HOVER_MOVE:
             // Consume all valid move events before timeout.
             return true;
-        case MotionEventCompatUtils.ACTION_HOVER_EXIT:
+        case MotionEvent.ACTION_HOVER_EXIT:
             // Ignore exit events outside the flick radius.
             if (distanceSquare < mFlickRadiusSquare) {
                 clearFlick(true);
@@ -171,10 +169,8 @@
      * Computes the direction of a flick gesture and forwards it to
      * {@link #onFlick(MotionEvent, MotionEvent, int)} for handling.
      *
-     * @param e1 The {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event
-     *            where the flick started.
-     * @param e2 The {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event
-     *            where the flick ended.
+     * @param e1 The {@link MotionEvent#ACTION_HOVER_ENTER} event where the flick started.
+     * @param e2 The {@link MotionEvent#ACTION_HOVER_EXIT} event where the flick ended.
      * @return {@code true} if the flick event was handled.
      */
     private boolean dispatchFlick(MotionEvent e1, MotionEvent e2) {
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 7302830..3d861c2 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -17,8 +17,8 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -28,6 +28,8 @@
 import java.util.HashMap;
 
 public class KeyCodeDescriptionMapper {
+    private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName();
+
     // The resource ID of the string spoken for obscured keys
     private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
 
@@ -39,14 +41,8 @@
     // Map of key codes to spoken description resource IDs
     private final HashMap<Integer, Integer> mKeyCodeMap;
 
-    // Map of shifted key codes to spoken description resource IDs
-    private final HashMap<Integer, Integer> mShiftedKeyCodeMap;
-
-    // Map of shift-locked key codes to spoken description resource IDs
-    private final HashMap<Integer, Integer> mShiftLockedKeyCodeMap;
-
-    public static void init(Context context, SharedPreferences prefs) {
-        sInstance.initInternal(context, prefs);
+    public static void init() {
+        sInstance.initInternal();
     }
 
     public static KeyCodeDescriptionMapper getInstance() {
@@ -56,40 +52,15 @@
     private KeyCodeDescriptionMapper() {
         mKeyLabelMap = new HashMap<CharSequence, Integer>();
         mKeyCodeMap = new HashMap<Integer, Integer>();
-        mShiftedKeyCodeMap = new HashMap<Integer, Integer>();
-        mShiftLockedKeyCodeMap = new HashMap<Integer, Integer>();
     }
 
-    private void initInternal(Context context, SharedPreferences prefs) {
+    private void initInternal() {
         // Manual label substitutions for key labels with no string resource
         mKeyLabelMap.put(":-)", R.string.spoken_description_smiley);
 
         // Symbols that most TTS engines can't speak
-        mKeyCodeMap.put((int) '.', R.string.spoken_description_period);
-        mKeyCodeMap.put((int) ',', R.string.spoken_description_comma);
-        mKeyCodeMap.put((int) '(', R.string.spoken_description_left_parenthesis);
-        mKeyCodeMap.put((int) ')', R.string.spoken_description_right_parenthesis);
-        mKeyCodeMap.put((int) ':', R.string.spoken_description_colon);
-        mKeyCodeMap.put((int) ';', R.string.spoken_description_semicolon);
-        mKeyCodeMap.put((int) '!', R.string.spoken_description_exclamation_mark);
-        mKeyCodeMap.put((int) '?', R.string.spoken_description_question_mark);
-        mKeyCodeMap.put((int) '\"', R.string.spoken_description_double_quote);
-        mKeyCodeMap.put((int) '\'', R.string.spoken_description_single_quote);
-        mKeyCodeMap.put((int) '*', R.string.spoken_description_star);
-        mKeyCodeMap.put((int) '#', R.string.spoken_description_pound);
         mKeyCodeMap.put((int) ' ', R.string.spoken_description_space);
 
-        // Non-ASCII symbols (must use escape codes!)
-        mKeyCodeMap.put((int) '\u2022', R.string.spoken_description_dot);
-        mKeyCodeMap.put((int) '\u221A', R.string.spoken_description_square_root);
-        mKeyCodeMap.put((int) '\u03C0', R.string.spoken_description_pi);
-        mKeyCodeMap.put((int) '\u0394', R.string.spoken_description_delta);
-        mKeyCodeMap.put((int) '\u2122', R.string.spoken_description_trademark);
-        mKeyCodeMap.put((int) '\u2105', R.string.spoken_description_care_of);
-        mKeyCodeMap.put((int) '\u2026', R.string.spoken_description_ellipsis);
-        mKeyCodeMap.put((int) '\u201E', R.string.spoken_description_low_double_quote);
-        mKeyCodeMap.put((int) '\uFF0A', R.string.spoken_description_star);
-
         // Special non-character codes defined in Keyboard
         mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete);
         mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return);
@@ -98,12 +69,6 @@
         mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic);
         mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol);
         mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab);
-
-        // Shifted versions of non-character codes defined in Keyboard
-        mShiftedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift_shifted);
-
-        // Shift-locked versions of non-character codes defined in Keyboard
-        mShiftLockedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_caps_lock);
     }
 
     /**
@@ -125,27 +90,31 @@
      * @return a character sequence describing the action performed by pressing
      *         the key
      */
-    public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key,
+    public String getDescriptionForKey(Context context, Keyboard keyboard, Key key,
             boolean shouldObscure) {
-        if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
-            final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
+        final int code = key.mCode;
+
+        if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+            final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
             if (description != null)
                 return description;
         }
 
+        if (code == Keyboard.CODE_SHIFT) {
+            return getDescriptionForShiftKey(context, keyboard);
+        }
+
         if (!TextUtils.isEmpty(key.mLabel)) {
             final String label = key.mLabel.toString().trim();
 
+            // First, attempt to map the label to a pre-defined description.
             if (mKeyLabelMap.containsKey(label)) {
                 return context.getString(mKeyLabelMap.get(label));
-            } else if (label.length() == 1
-                    || (keyboard.isManualTemporaryUpperCase() && !TextUtils
-                            .isEmpty(key.mHintLabel))) {
-                return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
-            } else {
-                return label;
             }
-        } else if (key.mCode != Keyboard.CODE_DUMMY) {
+        }
+
+        // Just attempt to speak the description.
+        if (key.mCode != Keyboard.CODE_UNSPECIFIED) {
             return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
         }
 
@@ -162,36 +131,64 @@
      * @return a character sequence describing the action performed by pressing
      *         the key
      */
-    private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) {
-        final KeyboardId id = keyboard.mId;
+    private String getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) {
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final int resId;
 
-        if (id.isAlphabetKeyboard()) {
-            return context.getString(R.string.spoken_description_to_symbol);
-        } else if (id.isSymbolsKeyboard()) {
-            return context.getString(R.string.spoken_description_to_alpha);
-        } else if (id.isPhoneShiftKeyboard()) {
-            return context.getString(R.string.spoken_description_to_numeric);
-        } else if (id.isPhoneKeyboard()) {
-            return context.getString(R.string.spoken_description_to_symbol);
-        } else {
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET:
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_to_symbol;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_to_alpha;
+            break;
+        case KeyboardId.ELEMENT_PHONE:
+            resId = R.string.spoken_description_to_symbol;
+            break;
+        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
+            resId = R.string.spoken_description_to_numeric;
+            break;
+        default:
+            Log.e(TAG, "Missing description for keyboard element ID:" + elementId);
             return null;
         }
+
+        return context.getString(resId);
     }
 
     /**
-     * Returns the keycode for the specified key given the current keyboard
-     * state.
+     * Returns a context-sensitive description of the "Shift" key.
      *
+     * @param context The package's context.
      * @param keyboard The keyboard on which the key resides.
-     * @param key The key from which to obtain a key code.
-     * @return the key code for the specified key
+     * @return A context-sensitive description of the "Shift" key.
      */
-    private int getCorrectKeyCode(Keyboard keyboard, Key key) {
-        if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLabel)) {
-            return key.mHintLabel.charAt(0);
-        } else {
-            return key.mCode;
+    private String getDescriptionForShiftKey(Context context, Keyboard keyboard) {
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final int resId;
+
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_caps_lock;
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_shift_shifted;
+            break;
+        default:
+            resId = R.string.spoken_description_shift;
         }
+
+        return context.getString(resId);
     }
 
     /**
@@ -215,15 +212,9 @@
      * @return a character sequence describing the action performed by pressing
      *         the key
      */
-    private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key,
+    private String getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key,
             boolean shouldObscure) {
-        final int code = getCorrectKeyCode(keyboard, key);
-
-        if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) {
-            return context.getString(mShiftLockedKeyCodeMap.get(code));
-        } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) {
-            return context.getString(mShiftedKeyCodeMap.get(code));
-        }
+        final int code = key.mCode;
 
         // If the key description should be obscured, now is the time to do it.
         final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
diff --git a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
deleted file mode 100644
index 6594935..0000000
--- a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.util.Log;
-
-public abstract class AbstractCompatWrapper {
-    private static final String TAG = AbstractCompatWrapper.class.getSimpleName();
-    protected final Object mObj;
-
-    public AbstractCompatWrapper(Object obj) {
-        if (obj == null) {
-            Log.e(TAG, "Invalid input to AbstructCompatWrapper");
-        }
-        mObj = obj;
-    }
-
-    public Object getOriginalObject() {
-        return mObj;
-    }
-
-    public boolean hasOriginalObject() {
-        return mObj != null;
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java b/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
deleted file mode 100644
index 2fa9d87..0000000
--- a/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-public class AccessibilityEventCompatUtils {
-    public static final int TYPE_VIEW_HOVER_ENTER = 0x80;
-    public static final int TYPE_VIEW_HOVER_EXIT = 0x100;
-}
diff --git a/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
deleted file mode 100644
index a30af0f..0000000
--- a/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.view.accessibility.AccessibilityManager;
-
-import java.lang.reflect.Method;
-
-public class AccessibilityManagerCompatWrapper {
-    private static final Method METHOD_isTouchExplorationEnabled = CompatUtils.getMethod(
-            AccessibilityManager.class, "isTouchExplorationEnabled");
-
-    private final AccessibilityManager mManager;
-
-    public AccessibilityManagerCompatWrapper(AccessibilityManager manager) {
-        mManager = manager;
-    }
-
-    public boolean isTouchExplorationEnabled() {
-        return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isTouchExplorationEnabled);
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java b/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
deleted file mode 100644
index f6afbcf..0000000
--- a/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-public class ArraysCompatUtils {
-    private static final Method METHOD_Arrays_binarySearch = CompatUtils
-            .getMethod(Arrays.class, "binarySearch", int[].class, int.class, int.class, int.class);
-
-    public static int binarySearch(int[] array, int startIndex, int endIndex, int value) {
-        if (METHOD_Arrays_binarySearch != null) {
-            final Object index = CompatUtils.invoke(null, 0, METHOD_Arrays_binarySearch,
-                    array, startIndex, endIndex, value);
-            return (Integer)index;
-        } else {
-            return compatBinarySearch(array, startIndex, endIndex, value);
-        }
-    }
-
-    /* package */ static int compatBinarySearch(int[] array, int startIndex, int endIndex,
-            int value) {
-        if (startIndex > endIndex) throw new IllegalArgumentException();
-        if (startIndex < 0 || endIndex > array.length) throw new ArrayIndexOutOfBoundsException();
-
-        final int work[] = new int[endIndex - startIndex];
-        System.arraycopy(array, startIndex, work, 0, work.length);
-        final int index = Arrays.binarySearch(work, value);
-        if (index >= 0) {
-            return index + startIndex;
-        } else {
-            return ~(~index + startIndex);
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index b42633c..ce427e9 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -23,8 +23,6 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
 
 public class CompatUtils {
     private static final String TAG = CompatUtils.class.getSimpleName();
@@ -32,28 +30,17 @@
     // TODO: Can these be constants instead of literal String constants?
     private static final String INPUT_METHOD_SUBTYPE_SETTINGS =
             "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
-    private static final String INPUT_LANGUAGE_SELECTION =
-            "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
 
     public static Intent getInputLanguageSelectionIntent(String inputMethodId,
             int flagsForSubtypeSettings) {
-        final String action;
-        Intent intent;
-        if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED
-                /* android.os.Build.VERSION_CODES.HONEYCOMB */
-                && android.os.Build.VERSION.SDK_INT >=  11) {
-            // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
-            action = INPUT_METHOD_SUBTYPE_SETTINGS;
-            intent = new Intent(action);
-            if (!TextUtils.isEmpty(inputMethodId)) {
-                intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
-            }
-            if (flagsForSubtypeSettings > 0) {
-                intent.setFlags(flagsForSubtypeSettings);
-            }
-        } else {
-            action = INPUT_LANGUAGE_SELECTION;
-            intent = new Intent(action);
+        // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+        final String action = INPUT_METHOD_SUBTYPE_SETTINGS;
+        final Intent intent = new Intent(action);
+        if (!TextUtils.isEmpty(inputMethodId)) {
+            intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
+        }
+        if (flagsForSubtypeSettings > 0) {
+            intent.setFlags(flagsForSubtypeSettings);
         }
         return intent;
     }
@@ -142,15 +129,4 @@
             Log.e(TAG, "Exception in setFieldValue: " + e.getClass().getSimpleName());
         }
     }
-
-    public static List<InputMethodSubtypeCompatWrapper> copyInputMethodSubtypeListToWrapper(
-            Object listObject) {
-        if (!(listObject instanceof List<?>)) return null;
-        final List<InputMethodSubtypeCompatWrapper> subtypes =
-                new ArrayList<InputMethodSubtypeCompatWrapper>();
-        for (Object o: (List<?>)listObject) {
-            subtypes.add(new InputMethodSubtypeCompatWrapper(o));
-        }
-        return subtypes;
-    }
 }
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index bcdcef7..08c246f 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -17,86 +17,65 @@
 package com.android.inputmethod.compat;
 
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
 
 import java.lang.reflect.Field;
 
 public class EditorInfoCompatUtils {
-    private static final Field FIELD_IME_FLAG_NAVIGATE_NEXT = CompatUtils.getField(
-            EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT");
-    private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField(
-            EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS");
-    private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField(
-            EditorInfo.class, "IME_ACTION_PREVIOUS");
-    private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT);
-    private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS);
-    private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS);
+    // EditorInfo.IME_FLAG_FORCE_ASCII has been introduced since API#16 (JellyBean).
+    private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_FORCE_ASCII");
+    private static final Integer OBJ_IME_FLAG_FORCE_ASCII = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_FLAG_FORCE_ASCII);
 
-    public static boolean hasFlagNavigateNext(int imeOptions) {
-        if (OBJ_IME_FLAG_NAVIGATE_NEXT == null)
+    private EditorInfoCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static boolean hasFlagForceAscii(int imeOptions) {
+        if (OBJ_IME_FLAG_FORCE_ASCII == null)
             return false;
-        return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0;
+        return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0;
     }
 
-    public static boolean hasFlagNavigatePrevious(int imeOptions) {
-        if (OBJ_IME_FLAG_NAVIGATE_PREVIOUS == null)
-            return false;
-        return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0;
-    }
-
-    public static void performEditorActionNext(InputConnection ic) {
-        ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
-    }
-
-    public static void performEditorActionPrevious(InputConnection ic) {
-        if (OBJ_IME_ACTION_PREVIOUS == null)
-            return;
-        ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS);
+    public static String imeActionName(int imeOptions) {
+        final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION;
+        switch (actionId) {
+        case EditorInfo.IME_ACTION_UNSPECIFIED:
+            return "actionUnspecified";
+        case EditorInfo.IME_ACTION_NONE:
+            return "actionNone";
+        case EditorInfo.IME_ACTION_GO:
+            return "actionGo";
+        case EditorInfo.IME_ACTION_SEARCH:
+            return "actionSearch";
+        case EditorInfo.IME_ACTION_SEND:
+            return "actionSend";
+        case EditorInfo.IME_ACTION_NEXT:
+            return "actionNext";
+        case EditorInfo.IME_ACTION_DONE:
+            return "actionDone";
+        case EditorInfo.IME_ACTION_PREVIOUS:
+            return "actionPrevious";
+        default:
+            return "actionUnknown(" + actionId + ")";
+        }
     }
 
     public static String imeOptionsName(int imeOptions) {
-        if (imeOptions == -1)
-            return null;
-        final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION;
-        final String action;
-        switch (actionId) {
-            case EditorInfo.IME_ACTION_UNSPECIFIED:
-                action = "actionUnspecified";
-                break;
-            case EditorInfo.IME_ACTION_NONE:
-                action = "actionNone";
-                break;
-            case EditorInfo.IME_ACTION_GO:
-                action = "actionGo";
-                break;
-            case EditorInfo.IME_ACTION_SEARCH:
-                action = "actionSearch";
-                break;
-            case EditorInfo.IME_ACTION_SEND:
-                action = "actionSend";
-                break;
-            case EditorInfo.IME_ACTION_NEXT:
-                action = "actionNext";
-                break;
-            case EditorInfo.IME_ACTION_DONE:
-                action = "actionDone";
-                break;
-            default: {
-                if (OBJ_IME_ACTION_PREVIOUS != null && actionId == OBJ_IME_ACTION_PREVIOUS) {
-                    action = "actionPrevious";
-                } else {
-                    action = "actionUnknown(" + actionId + ")";
-                }
-                break;
-            }
-        }
+        final String action = imeActionName(imeOptions);
+        final StringBuilder flags = new StringBuilder();
         if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
-            return "flagNoEnterAction|" + action;
-        } else {
-            return action;
+            flags.append("flagNoEnterAction|");
         }
+        if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
+            flags.append("flagNavigateNext|");
+        }
+        if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0) {
+            flags.append("flagNavigatePrevious|");
+        }
+        if (hasFlagForceAscii(imeOptions)) {
+            flags.append("flagForceAscii|");
+        }
+        return (action != null) ? flags + action : flags.toString();
     }
 }
diff --git a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java
deleted file mode 100644
index 7d00b60..0000000
--- a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import com.android.inputmethod.latin.EditingUtils.SelectedWord;
-
-import android.view.inputmethod.InputConnection;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-
-public class InputConnectionCompatUtils {
-    private static final Class<?> CLASS_CorrectionInfo = CompatUtils
-            .getClass("android.view.inputmethod.CorrectionInfo");
-    private static final Class<?>[] INPUT_TYPE_CorrectionInfo = new Class<?>[] { int.class,
-            CharSequence.class, CharSequence.class };
-    private static final Constructor<?> CONSTRUCTOR_CorrectionInfo = CompatUtils
-            .getConstructor(CLASS_CorrectionInfo, INPUT_TYPE_CorrectionInfo);
-    private static final Method METHOD_InputConnection_commitCorrection = CompatUtils
-            .getMethod(InputConnection.class, "commitCorrection", CLASS_CorrectionInfo);
-    private static final Method METHOD_getSelectedText = CompatUtils
-            .getMethod(InputConnection.class, "getSelectedText", int.class);
-    private static final Method METHOD_setComposingRegion = CompatUtils
-            .getMethod(InputConnection.class, "setComposingRegion", int.class, int.class);
-    public static final boolean RECORRECTION_SUPPORTED;
-
-    static {
-        RECORRECTION_SUPPORTED = METHOD_getSelectedText != null
-                && METHOD_setComposingRegion != null;
-    }
-
-    public static void commitCorrection(InputConnection ic, int offset, CharSequence oldText,
-            CharSequence newText) {
-        if (ic == null || CONSTRUCTOR_CorrectionInfo == null
-                || METHOD_InputConnection_commitCorrection == null) {
-            return;
-        }
-        Object[] args = { offset, oldText, newText };
-        Object correctionInfo = CompatUtils.newInstance(CONSTRUCTOR_CorrectionInfo, args);
-        if (correctionInfo != null) {
-            CompatUtils.invoke(ic, null, METHOD_InputConnection_commitCorrection,
-                    correctionInfo);
-        }
-    }
-
-
-    /**
-     * Returns the selected text between the selStart and selEnd positions.
-     */
-    public static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) {
-        // Use reflection, for backward compatibility
-        return (CharSequence) CompatUtils.invoke(
-                ic, null, METHOD_getSelectedText, 0);
-    }
-
-    /**
-     * Tries to set the text into composition mode if there is support for it in the framework.
-     */
-    public static void underlineWord(InputConnection ic, SelectedWord word) {
-        // Use reflection, for backward compatibility
-        // If method not found, there's nothing we can do. It still works but just wont underline
-        // the word.
-        CompatUtils.invoke(
-                ic, null, METHOD_setComposingRegion, word.mStart, word.mEnd);
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java
deleted file mode 100644
index 8315598..0000000
--- a/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.view.inputmethod.InputMethodInfo;
-
-import java.lang.reflect.Method;
-
-public class InputMethodInfoCompatWrapper {
-    private final InputMethodInfo mImi;
-    private static final Method METHOD_getSubtypeAt = CompatUtils.getMethod(
-            InputMethodInfo.class, "getSubtypeAt", int.class);
-    private static final Method METHOD_getSubtypeCount = CompatUtils.getMethod(
-            InputMethodInfo.class, "getSubtypeCount");
-
-    public InputMethodInfoCompatWrapper(InputMethodInfo imi) {
-        mImi = imi;
-    }
-
-    public InputMethodInfo getInputMethodInfo() {
-        return mImi;
-    }
-
-    public String getId() {
-        return mImi.getId();
-    }
-
-    public String getPackageName() {
-        return mImi.getPackageName();
-    }
-
-    public ServiceInfo getServiceInfo() {
-        return mImi.getServiceInfo();
-    }
-
-    public int getSubtypeCount() {
-        return (Integer) CompatUtils.invoke(mImi, 0, METHOD_getSubtypeCount);
-    }
-
-    public InputMethodSubtypeCompatWrapper getSubtypeAt(int index) {
-        return new InputMethodSubtypeCompatWrapper(CompatUtils.invoke(mImi, null,
-                METHOD_getSubtypeAt, index));
-    }
-
-    public CharSequence loadLabel(PackageManager pm) {
-        return mImi.loadLabel(pm);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof InputMethodInfoCompatWrapper) {
-            return mImi.equals(((InputMethodInfoCompatWrapper)o).mImi);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return mImi.hashCode();
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 51dc4cd..ffed820 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -16,75 +16,33 @@
 
 package com.android.inputmethod.compat;
 
-import android.app.AlertDialog;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
+import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
-
-import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
+import android.view.inputmethod.InputMethodSubtype;
 
 import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
 // TODO: Override this class with the concrete implementation if we need to take care of the
 // performance.
 public class InputMethodManagerCompatWrapper {
     private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
-    private static final Method METHOD_getCurrentInputMethodSubtype =
-            CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype");
-    private static final Method METHOD_getEnabledInputMethodSubtypeList =
-            CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList",
-                    InputMethodInfo.class, boolean.class);
-    private static final Method METHOD_getShortcutInputMethodsAndSubtypes =
-            CompatUtils.getMethod(InputMethodManager.class, "getShortcutInputMethodsAndSubtypes");
-    private static final Method METHOD_setInputMethodAndSubtype =
-            CompatUtils.getMethod(
-                    InputMethodManager.class, "setInputMethodAndSubtype", IBinder.class,
-                    String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype);
-    private static final Method METHOD_switchToLastInputMethod = CompatUtils.getMethod(
-            InputMethodManager.class, "switchToLastInputMethod", IBinder.class);
+    private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod(
+            InputMethodManager.class, "switchToNextInputMethod", IBinder.class, Boolean.TYPE);
 
     private static final InputMethodManagerCompatWrapper sInstance =
             new InputMethodManagerCompatWrapper();
 
-    public static final boolean SUBTYPE_SUPPORTED;
-
-    static {
-        // This static initializer guarantees that METHOD_getShortcutInputMethodsAndSubtypes is
-        // already instantiated.
-        SUBTYPE_SUPPORTED = METHOD_getShortcutInputMethodsAndSubtypes != null;
-    }
-
-    // For the compatibility, IMM will create dummy subtypes if subtypes are not found.
-    // This is required to be false if the current behavior is broken. For now, it's ok to be true.
-    public static final boolean FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES =
-            !InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED;
-    private static final String VOICE_MODE = "voice";
-    private static final String KEYBOARD_MODE = "keyboard";
-
-    private InputMethodServiceCompatWrapper mService;
     private InputMethodManager mImm;
-    private PackageManager mPackageManager;
-    private ApplicationInfo mApplicationInfo;
-    private LanguageSwitcherProxy mLanguageSwitcherProxy;
-    private String mLatinImePackageName;
+
+    private InputMethodManagerCompatWrapper() {
+        // This wrapper class is not publicly instantiable.
+    }
 
     public static InputMethodManagerCompatWrapper getInstance() {
         if (sInstance.mImm == null)
@@ -92,228 +50,49 @@
         return sInstance;
     }
 
-    public static void init(InputMethodServiceCompatWrapper service) {
-        sInstance.mService = service;
+    public static void init(InputMethodService service) {
         sInstance.mImm = (InputMethodManager) service.getSystemService(
                 Context.INPUT_METHOD_SERVICE);
-        sInstance.mLatinImePackageName = service.getPackageName();
-        sInstance.mPackageManager = service.getPackageManager();
-        sInstance.mApplicationInfo = service.getApplicationInfo();
-        sInstance.mLanguageSwitcherProxy = LanguageSwitcherProxy.getInstance();
     }
 
-    public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() {
-        if (!SUBTYPE_SUPPORTED) {
-            return new InputMethodSubtypeCompatWrapper(
-                    0, 0, mLanguageSwitcherProxy.getInputLocale().toString(), KEYBOARD_MODE, "");
-        }
-        Object o = CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype);
-        return new InputMethodSubtypeCompatWrapper(o);
+    public InputMethodSubtype getCurrentInputMethodSubtype() {
+        return mImm.getCurrentInputMethodSubtype();
     }
 
-    public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
-            InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
-        if (!SUBTYPE_SUPPORTED) {
-            String[] languages = mLanguageSwitcherProxy.getEnabledLanguages(
-                    allowsImplicitlySelectedSubtypes);
-            List<InputMethodSubtypeCompatWrapper> subtypeList =
-                    new ArrayList<InputMethodSubtypeCompatWrapper>();
-            for (String lang: languages) {
-                subtypeList.add(new InputMethodSubtypeCompatWrapper(0, 0, lang, KEYBOARD_MODE, ""));
-            }
-            return subtypeList;
-        }
-        Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList,
-                (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes);
-        if (retval == null || !(retval instanceof List<?>) || ((List<?>)retval).isEmpty()) {
-            if (!FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
-                // Returns an empty list
-                return Collections.emptyList();
-            }
-            // Creates dummy subtypes
-            @SuppressWarnings("unused")
-            List<InputMethodSubtypeCompatWrapper> subtypeList =
-                    new ArrayList<InputMethodSubtypeCompatWrapper>();
-            InputMethodSubtypeCompatWrapper keyboardSubtype = getLastResortSubtype(KEYBOARD_MODE);
-            InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE);
-            if (keyboardSubtype != null) {
-                subtypeList.add(keyboardSubtype);
-            }
-            if (voiceSubtype != null) {
-                subtypeList.add(voiceSubtype);
-            }
-            return subtypeList;
-        }
-        return CompatUtils.copyInputMethodSubtypeListToWrapper(retval);
+    public InputMethodSubtype getLastInputMethodSubtype() {
+        return mImm.getLastInputMethodSubtype();
     }
 
-    private InputMethodInfoCompatWrapper getLatinImeInputMethodInfo() {
-        if (TextUtils.isEmpty(mLatinImePackageName))
-            return null;
-        return Utils.getInputMethodInfo(this, mLatinImePackageName);
+    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
+            InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
+        return mImm.getEnabledInputMethodSubtypeList(imi, allowsImplicitlySelectedSubtypes);
     }
 
-    @SuppressWarnings("unused")
-    private InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
-        if (VOICE_MODE.equals(mode) && !FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES)
-            return null;
-        Locale inputLocale = SubtypeSwitcher.getInstance().getInputLocale();
-        if (inputLocale == null)
-            return null;
-        return new InputMethodSubtypeCompatWrapper(0, 0, inputLocale.toString(), mode, "");
-    }
-
-    public Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
-            getShortcutInputMethodsAndSubtypes() {
-        Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes);
-        if (retval == null || !(retval instanceof Map<?, ?>) || ((Map<?, ?>)retval).isEmpty()) {
-            if (!FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
-                // Returns an empty map
-                return Collections.emptyMap();
-            }
-            // Creates dummy subtypes
-            @SuppressWarnings("unused")
-            InputMethodInfoCompatWrapper imi = getLatinImeInputMethodInfo();
-            InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE);
-            if (imi != null && voiceSubtype != null) {
-                Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
-                        shortcutMap =
-                                new HashMap<InputMethodInfoCompatWrapper,
-                                        List<InputMethodSubtypeCompatWrapper>>();
-                List<InputMethodSubtypeCompatWrapper> subtypeList =
-                        new ArrayList<InputMethodSubtypeCompatWrapper>();
-                subtypeList.add(voiceSubtype);
-                shortcutMap.put(imi, subtypeList);
-                return shortcutMap;
-            } else {
-                return Collections.emptyMap();
-            }
-        }
-        Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap =
-                new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>();
-        final Map<?, ?> retvalMap = (Map<?, ?>)retval;
-        for (Object key : retvalMap.keySet()) {
-            if (!(key instanceof InputMethodInfo)) {
-                Log.e(TAG, "Class type error.");
-                return null;
-            }
-            shortcutMap.put(new InputMethodInfoCompatWrapper((InputMethodInfo)key),
-                    CompatUtils.copyInputMethodSubtypeListToWrapper(retvalMap.get(key)));
-        }
-        return shortcutMap;
+    public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
+        return mImm.getShortcutInputMethodsAndSubtypes();
     }
 
     // We don't call this method when we switch between subtypes within this IME.
-    public void setInputMethodAndSubtype(
-            IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) {
-        // TODO: Support subtype change on non-subtype-supported platform.
-        if (subtype != null && subtype.hasOriginalObject()) {
-            CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype,
-                    token, id, subtype.getOriginalObject());
-        } else {
-            mImm.setInputMethod(token, id);
-        }
+    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+        mImm.setInputMethodAndSubtype(token, id, subtype);
     }
 
     public boolean switchToLastInputMethod(IBinder token) {
-        if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) {
-            return true;
-        }
-        return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
+        return mImm.switchToLastInputMethod(token);
     }
 
-    public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() {
+    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+        return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToNextInputMethod, token,
+                onlyCurrentIme);
+    }
+
+    public List<InputMethodInfo> getEnabledInputMethodList() {
         if (mImm == null) return null;
-        List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>();
-        for (InputMethodInfo imi : mImm.getEnabledInputMethodList()) {
-            imis.add(new InputMethodInfoCompatWrapper(imi));
-        }
-        return imis;
+        return mImm.getEnabledInputMethodList();
     }
 
     public void showInputMethodPicker() {
         if (mImm == null) return;
-        if (SUBTYPE_SUPPORTED) {
-            mImm.showInputMethodPicker();
-            return;
-        }
-
-        // The code below are based on {@link InputMethodManager#showInputMethodMenuInternal}.
-
-        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
-                this, mLatinImePackageName);
-        final List<InputMethodSubtypeCompatWrapper> myImsList = getEnabledInputMethodSubtypeList(
-                myImi, true);
-        final InputMethodSubtypeCompatWrapper currentIms = getCurrentInputMethodSubtype();
-        final List<InputMethodInfoCompatWrapper> imiList = getEnabledInputMethodList();
-        imiList.remove(myImi);
-        Collections.sort(imiList, new Comparator<InputMethodInfoCompatWrapper>() {
-            @Override
-            public int compare(InputMethodInfoCompatWrapper imi1,
-                    InputMethodInfoCompatWrapper imi2) {
-                final CharSequence imiId1 = imi1.loadLabel(mPackageManager) + "/" + imi1.getId();
-                final CharSequence imiId2 = imi2.loadLabel(mPackageManager) + "/" + imi2.getId();
-                return imiId1.toString().compareTo(imiId2.toString());
-            }
-        });
-
-        final int myImsCount = myImsList.size();
-        final int imiCount = imiList.size();
-        final CharSequence[] items = new CharSequence[myImsCount + imiCount];
-
-        int checkedItem = 0;
-        int index = 0;
-        final CharSequence myImiLabel = myImi.loadLabel(mPackageManager);
-        for (int i = 0; i < myImsCount; i++) {
-            InputMethodSubtypeCompatWrapper ims = myImsList.get(i);
-            if (currentIms.equals(ims))
-                checkedItem = index;
-            final CharSequence title = TextUtils.concat(
-                    ims.getDisplayName(mService, mLatinImePackageName, mApplicationInfo),
-                    " (" + myImiLabel, ")");
-            items[index] = title;
-            index++;
-        }
-
-        for (int i = 0; i < imiCount; i++) {
-            final InputMethodInfoCompatWrapper imi = imiList.get(i);
-            final CharSequence title = imi.loadLabel(mPackageManager);
-            items[index] = title;
-            index++;
-        }
-
-        final OnClickListener buttonListener = new OnClickListener() {
-            @Override
-            public void onClick(DialogInterface di, int whichButton) {
-                final Intent intent = new Intent("android.settings.INPUT_METHOD_SETTINGS");
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                mService.startActivity(intent);
-            }
-        };
-        final InputMethodServiceCompatWrapper service = mService;
-        final IBinder token = service.getWindow().getWindow().getAttributes().token;
-        final OnClickListener selectionListener = new OnClickListener() {
-            @Override
-            public void onClick(DialogInterface di, int which) {
-                di.dismiss();
-                if (which < myImsCount) {
-                    final int imsIndex = which;
-                    final InputMethodSubtypeCompatWrapper ims = myImsList.get(imsIndex);
-                    service.notifyOnCurrentInputMethodSubtypeChanged(ims);
-                } else {
-                    final int imiIndex = which - myImsCount;
-                    final InputMethodInfoCompatWrapper imi = imiList.get(imiIndex);
-                    setInputMethodAndSubtype(token, imi.getId(), null);
-                }
-            }
-        };
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mService)
-                .setTitle(mService.getString(R.string.selectInputMethod))
-                .setNeutralButton(R.string.configure_input_method, buttonListener)
-                .setSingleChoiceItems(items, checkedItem, selectionListener);
-        mService.showOptionDialogInternal(builder.create());
+        mImm.showInputMethodPicker();
     }
 }
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
deleted file mode 100644
index 8e2ee0f..0000000
--- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.app.AlertDialog;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-
-public class InputMethodServiceCompatWrapper extends InputMethodService {
-    // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10
-    // or previous. Note that InputMethodSubtype was added in the API level 11.
-    // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged().
-    // For the API level 10 or previous, we handle the "subtype changed" events by ourselves
-    // without having support from framework -- onCurrentInputMethodSubtypeChanged().
-    public static final boolean CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED = true;
-
-    private InputMethodManagerCompatWrapper mImm;
-
-    // For compatibility of {@link InputMethodManager#showInputMethodPicker}.
-    // TODO: Move this variable back to LatinIME when this compatibility wrapper is removed.
-    protected AlertDialog mOptionsDialog;
-
-    public void showOptionDialogInternal(AlertDialog dialog) {
-        final IBinder windowToken = KeyboardSwitcher.getInstance().getKeyboardView()
-                .getWindowToken();
-        if (windowToken == null) return;
-
-        dialog.setCancelable(true);
-        dialog.setCanceledOnTouchOutside(true);
-
-        final Window window = dialog.getWindow();
-        final WindowManager.LayoutParams lp = window.getAttributes();
-        lp.token = windowToken;
-        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-        window.setAttributes(lp);
-        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-
-        mOptionsDialog = dialog;
-        dialog.show();
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mImm = InputMethodManagerCompatWrapper.getInstance();
-    }
-
-    // When the API level is 10 or previous, notifyOnCurrentInputMethodSubtypeChanged should
-    // handle the event the current subtype was changed. LatinIME calls
-    // notifyOnCurrentInputMethodSubtypeChanged every time LatinIME
-    // changes the current subtype.
-    // This call is required to let LatinIME itself know a subtype changed
-    // event when the API level is 10 or previous.
-    @SuppressWarnings("unused")
-    public void notifyOnCurrentInputMethodSubtypeChanged(
-            InputMethodSubtypeCompatWrapper newSubtype) {
-        // Do nothing when the API level is 11 or later
-        // and FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES is not true
-        if (CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED && !InputMethodManagerCompatWrapper.
-                FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
-            return;
-        }
-        final InputMethodSubtypeCompatWrapper subtype = (newSubtype == null)
-                ? mImm.getCurrentInputMethodSubtype()
-                : newSubtype;
-        if (subtype != null) {
-            if (!InputMethodManagerCompatWrapper.FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES
-                    && !subtype.isDummy()) return;
-            if (!InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) {
-                LanguageSwitcherProxy.getInstance().setLocale(subtype.getLocale());
-            }
-            SubtypeSwitcher.getInstance().updateSubtype(subtype);
-        }
-    }
-
-    //////////////////////////////////////
-    // Functions using API v11 or later //
-    //////////////////////////////////////
-    @Override
-    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
-        // Do nothing when the API level is 10 or previous
-        if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return;
-        SubtypeSwitcher.getInstance().updateSubtype(
-                new InputMethodSubtypeCompatWrapper(subtype));
-    }
-
-    protected static void setTouchableRegionCompat(InputMethodService.Insets outInsets,
-            int x, int y, int width, int height) {
-        outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
-        outInsets.touchableRegion.set(x, y, width, height);
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
deleted file mode 100644
index a6bb83a..0000000
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.latin.LatinImeLogger;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.Locale;
-
-// TODO: Override this class with the concrete implementation if we need to take care of the
-// performance.
-public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper {
-    private static final boolean DBG = LatinImeLogger.sDBG;
-    private static final String TAG = InputMethodSubtypeCompatWrapper.class.getSimpleName();
-    private static final String DEFAULT_LOCALE = "en_US";
-    private static final String DEFAULT_MODE = "keyboard";
-
-    public static final Class<?> CLASS_InputMethodSubtype =
-            CompatUtils.getClass("android.view.inputmethod.InputMethodSubtype");
-    private static final Method METHOD_getNameResId =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getNameResId");
-    private static final Method METHOD_getIconResId =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getIconResId");
-    private static final Method METHOD_getLocale =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getLocale");
-    private static final Method METHOD_getMode =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getMode");
-    private static final Method METHOD_getExtraValue =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValue");
-    private static final Method METHOD_containsExtraValueKey =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "containsExtraValueKey", String.class);
-    private static final Method METHOD_getExtraValueOf =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class);
-    private static final Method METHOD_isAuxiliary =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "isAuxiliary");
-    private static final Method METHOD_getDisplayName =
-            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getDisplayName", Context.class,
-                    String.class, ApplicationInfo.class);
-
-    private final int mDummyNameResId;
-    private final int mDummyIconResId;
-    private final String mDummyLocale;
-    private final String mDummyMode;
-    private final String mDummyExtraValues;
-
-    public InputMethodSubtypeCompatWrapper(Object subtype) {
-        super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype))
-                ? subtype : null);
-        mDummyNameResId = 0;
-        mDummyIconResId = 0;
-        mDummyLocale = DEFAULT_LOCALE;
-        mDummyMode = DEFAULT_MODE;
-        mDummyExtraValues = "";
-    }
-
-    // Constructor for creating a dummy subtype.
-    public InputMethodSubtypeCompatWrapper(int nameResId, int iconResId, String locale,
-            String mode, String extraValues) {
-        super(null);
-        if (DBG) {
-            Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper");
-        }
-        mDummyNameResId = nameResId;
-        mDummyIconResId = iconResId;
-        mDummyLocale = locale != null ? locale : "";
-        mDummyMode = mode != null ? mode : "";
-        mDummyExtraValues = extraValues != null ? extraValues : "";
-    }
-
-    public int getNameResId() {
-        if (mObj == null) return mDummyNameResId;
-        return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId);
-    }
-
-    public int getIconResId() {
-        if (mObj == null) return mDummyIconResId;
-        return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId);
-    }
-
-    public String getLocale() {
-        if (mObj == null) return mDummyLocale;
-        final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale);
-        return s != null ? s : DEFAULT_LOCALE;
-    }
-
-    public String getMode() {
-        if (mObj == null) return mDummyMode;
-        String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode);
-        if (TextUtils.isEmpty(s)) return DEFAULT_MODE;
-        return s;
-    }
-
-    public String getExtraValue() {
-        if (mObj == null) return mDummyExtraValues;
-        return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue);
-    }
-
-    public boolean containsExtraValueKey(String key) {
-        return (Boolean)CompatUtils.invoke(mObj, false, METHOD_containsExtraValueKey, key);
-    }
-
-    public String getExtraValueOf(String key) {
-        return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValueOf, key);
-    }
-
-    public boolean isAuxiliary() {
-        return (Boolean)CompatUtils.invoke(mObj, false, METHOD_isAuxiliary);
-    }
-
-    public CharSequence getDisplayName(Context context, String packageName,
-            ApplicationInfo appInfo) {
-        if (mObj != null) {
-            return (CharSequence)CompatUtils.invoke(
-                    mObj, "", METHOD_getDisplayName, context, packageName, appInfo);
-        }
-
-        // The code below are based on {@link InputMethodSubtype#getDisplayName}.
-
-        final Locale locale = new Locale(getLocale());
-        final String localeStr = locale.getDisplayName();
-        if (getNameResId() == 0) {
-            return localeStr;
-        }
-        final CharSequence subtypeName = context.getText(getNameResId());
-        if (!TextUtils.isEmpty(localeStr)) {
-            return String.format(subtypeName.toString(), localeStr);
-        } else {
-            return localeStr;
-        }
-    }
-
-    public boolean isDummy() {
-        return !hasOriginalObject();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof InputMethodSubtypeCompatWrapper) {
-            InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o;
-            if (mObj == null) {
-                // easy check of dummy subtypes
-                return (mDummyNameResId == subtype.mDummyNameResId
-                        && mDummyIconResId == subtype.mDummyIconResId
-                        && mDummyLocale.equals(subtype.mDummyLocale)
-                        && mDummyMode.equals(subtype.mDummyMode)
-                        && mDummyExtraValues.equals(subtype.mDummyExtraValues));
-            }
-            return mObj.equals(subtype.getOriginalObject());
-        } else {
-            return mObj.equals(o);
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        if (mObj == null) {
-            return hashCodeInternal(mDummyNameResId, mDummyIconResId, mDummyLocale,
-                    mDummyMode, mDummyExtraValues);
-        }
-        return mObj.hashCode();
-    }
-
-    private static int hashCodeInternal(int nameResId, int iconResId, String locale,
-            String mode, String extraValue) {
-        return Arrays
-                .hashCode(new Object[] { nameResId, iconResId, locale, mode, extraValue });
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java
deleted file mode 100644
index 6c2f0f7..0000000
--- a/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.text.InputType;
-
-import java.lang.reflect.Field;
-
-public class InputTypeCompatUtils {
-    private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS =
-            CompatUtils.getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
-    private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = CompatUtils
-            .getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_PASSWORD");
-    private static final Field FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD = CompatUtils
-            .getField(InputType.class, "TYPE_NUMBER_VARIATION_PASSWORD");
-    private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS =
-            (Integer) CompatUtils.getFieldValue(null, null,
-                    FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
-    private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD =
-            (Integer) CompatUtils.getFieldValue(null, null,
-                    FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD);
-    private static final Integer OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD =
-            (Integer) CompatUtils.getFieldValue(null, null,
-                    FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD);
-    private static final int WEB_TEXT_PASSWORD_INPUT_TYPE;
-    private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE;
-    private static final int NUMBER_PASSWORD_INPUT_TYPE;
-    private static final int TEXT_PASSWORD_INPUT_TYPE =
-            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
-    private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE =
-            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
-
-    static {
-        WEB_TEXT_PASSWORD_INPUT_TYPE =
-            OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD != null
-                    ? InputType.TYPE_CLASS_TEXT | OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD
-                    : 0;
-        WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE =
-            OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != null
-                    ? InputType.TYPE_CLASS_TEXT
-                            | OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
-                    : 0;
-        NUMBER_PASSWORD_INPUT_TYPE =
-                OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD != null
-                        ? InputType.TYPE_CLASS_NUMBER | OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD
-                        : 0;
-    }
-
-    private static boolean isWebEditTextInputType(int inputType) {
-        return inputType == (InputType.TYPE_CLASS_TEXT
-                | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
-    }
-
-    private static boolean isWebPasswordInputType(int inputType) {
-        return WEB_TEXT_PASSWORD_INPUT_TYPE != 0
-                && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE;
-    }
-
-    private static boolean isWebEmailAddressInputType(int inputType) {
-        return WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE != 0
-                && inputType == WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE;
-    }
-
-    private static boolean isNumberPasswordInputType(int inputType) {
-        return NUMBER_PASSWORD_INPUT_TYPE != 0
-                && inputType == NUMBER_PASSWORD_INPUT_TYPE;
-    }
-
-    private static boolean isTextPasswordInputType(int inputType) {
-        return inputType == TEXT_PASSWORD_INPUT_TYPE;
-    }
-
-    private static boolean isWebEmailAddressVariation(int variation) {
-        return OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != null
-                && variation == OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
-    }
-
-    public static boolean isEmailVariation(int variation) {
-        return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
-                || isWebEmailAddressVariation(variation);
-    }
-
-    public static boolean isWebInputType(int inputType) {
-        final int maskedInputType =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return isWebEditTextInputType(maskedInputType) || isWebPasswordInputType(maskedInputType)
-                || isWebEmailAddressInputType(maskedInputType);
-    }
-
-    // Please refer to TextView.isPasswordInputType
-    public static boolean isPasswordInputType(int inputType) {
-        final int maskedInputType =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType)
-                || isNumberPasswordInputType(maskedInputType);
-    }
-
-    // Please refer to TextView.isVisiblePasswordInputType
-    public static boolean isVisiblePasswordInputType(int inputType) {
-        final int maskedInputType =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE;
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
deleted file mode 100644
index 8518a4a..0000000
--- a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-public class MotionEventCompatUtils {
-    public static final int ACTION_HOVER_MOVE = 0x7;
-    public static final int ACTION_HOVER_ENTER = 0x9;
-    public static final int ACTION_HOVER_EXIT = 0xA;
-}
diff --git a/java/src/com/android/inputmethod/compat/SharedPreferencesCompat.java b/java/src/com/android/inputmethod/compat/SharedPreferencesCompat.java
deleted file mode 100644
index 38736f3..0000000
--- a/java/src/com/android/inputmethod/compat/SharedPreferencesCompat.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.compat;
-
-import android.content.SharedPreferences;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Reflection utils to call SharedPreferences$Editor.apply when possible,
- * falling back to commit when apply isn't available.
- */
-public class SharedPreferencesCompat {
-    private static final Method sApplyMethod = findApplyMethod();
-
-    private static Method findApplyMethod() {
-        try {
-            return SharedPreferences.Editor.class.getMethod("apply");
-        } catch (NoSuchMethodException unused) {
-            // fall through
-        }
-        return null;
-    }
-
-    public static void apply(SharedPreferences.Editor editor) {
-        if (sApplyMethod != null) {
-            try {
-                sApplyMethod.invoke(editor);
-                return;
-            } catch (InvocationTargetException unused) {
-                // fall through
-            } catch (IllegalAccessException unused) {
-                // fall through
-            }
-        }
-        editor.commit();
-    }
-}
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 161ef09..5c351e4 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -48,30 +48,44 @@
             Context.class, Locale.class, String[].class, int.class, Class.class };
     private static final Constructor<?> CONSTRUCTOR_SuggestionSpan = CompatUtils
             .getConstructor(CLASS_SuggestionSpan, INPUT_TYPE_SuggestionSpan);
-    public static final Field FIELD_FLAG_AUTO_CORRECTION
-            = CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_AUTO_CORRECTION");
-    public static final Field FIELD_SUGGESTION_MAX_SIZE
+    public static final Field FIELD_FLAG_EASY_CORRECT =
+            CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_EASY_CORRECT");
+    public static final Field FIELD_FLAG_MISSPELLED =
+            CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_MISSPELLED");
+    public static final Field FIELD_FLAG_AUTO_CORRECTION =
+            CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_AUTO_CORRECTION");
+    public static final Field FIELD_SUGGESTIONS_MAX_SIZE
             = CompatUtils.getField(CLASS_SuggestionSpan, "SUGGESTIONS_MAX_SIZE");
+    public static final Integer OBJ_FLAG_EASY_CORRECT = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_FLAG_EASY_CORRECT);
+    public static final Integer OBJ_FLAG_MISSPELLED = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_FLAG_MISSPELLED);
     public static final Integer OBJ_FLAG_AUTO_CORRECTION = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_FLAG_AUTO_CORRECTION);;
-    public static final Integer OBJ_SUGGESTION_MAX_SIZE = (Integer) CompatUtils
-            .getFieldValue(null, null, FIELD_SUGGESTION_MAX_SIZE);;
+            .getFieldValue(null, null, FIELD_FLAG_AUTO_CORRECTION);
+    public static final Integer OBJ_SUGGESTIONS_MAX_SIZE = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_SUGGESTIONS_MAX_SIZE);
 
     static {
         SUGGESTION_SPAN_IS_SUPPORTED =
                 CLASS_SuggestionSpan != null && CONSTRUCTOR_SuggestionSpan != null;
         if (LatinImeLogger.sDBG) {
             if (SUGGESTION_SPAN_IS_SUPPORTED
-                    && (OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTION_MAX_SIZE == null)) {
+                    && (OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTIONS_MAX_SIZE == null
+                            || OBJ_FLAG_MISSPELLED == null || OBJ_FLAG_EASY_CORRECT == null)) {
                 throw new RuntimeException("Field is accidentially null.");
             }
         }
     }
 
+    private SuggestionSpanUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
     public static CharSequence getTextWithAutoCorrectionIndicatorUnderline(
             Context context, CharSequence text) {
         if (TextUtils.isEmpty(text) || CONSTRUCTOR_SuggestionSpan == null
-                || OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTION_MAX_SIZE == null) {
+                || OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTIONS_MAX_SIZE == null
+                || OBJ_FLAG_MISSPELLED == null || OBJ_FLAG_EASY_CORRECT == null) {
             return text;
         }
         final Spannable spannable = text instanceof Spannable
@@ -93,8 +107,7 @@
             CharSequence pickedWord, SuggestedWords suggestedWords) {
         if (TextUtils.isEmpty(pickedWord) || CONSTRUCTOR_SuggestionSpan == null
                 || suggestedWords == null || suggestedWords.size() == 0
-                || suggestedWords.getInfo(0).isObsoleteSuggestedWord()
-                || OBJ_SUGGESTION_MAX_SIZE == null) {
+                || OBJ_SUGGESTIONS_MAX_SIZE == null) {
             return pickedWord;
         }
 
@@ -105,18 +118,26 @@
             spannable = new SpannableString(pickedWord);
         }
         final ArrayList<String> suggestionsList = new ArrayList<String>();
+        boolean sameAsTyped = false;
         for (int i = 0; i < suggestedWords.size(); ++i) {
-            if (suggestionsList.size() >= OBJ_SUGGESTION_MAX_SIZE) {
+            if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
                 break;
             }
             final CharSequence word = suggestedWords.getWord(i);
             if (!TextUtils.equals(pickedWord, word)) {
                 suggestionsList.add(word.toString());
+            } else if (i == 0) {
+                sameAsTyped = true;
             }
         }
+        // TODO: Share the implementation for checking typed word validity between the IME
+        // and the spell checker.
+        final int flag = (sameAsTyped && !suggestedWords.mTypedWordValid)
+                ? (OBJ_FLAG_EASY_CORRECT | OBJ_FLAG_MISSPELLED)
+                : 0;
 
         final Object[] args =
-                { context, null, suggestionsList.toArray(new String[suggestionsList.size()]), 0,
+                { context, null, suggestionsList.toArray(new String[suggestionsList.size()]), flag,
                         (Class<?>) SuggestionSpanPickedNotificationReceiver.class };
         final Object ss = CompatUtils.newInstance(CONSTRUCTOR_SuggestionSpan, args);
         if (ss == null) {
diff --git a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
new file mode 100644
index 0000000..e5f9db2
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.view.textservice.SuggestionsInfo;
+
+import java.lang.reflect.Field;
+
+public class SuggestionsInfoCompatUtils {
+    private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = CompatUtils.getField(
+            SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS");
+    private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS);
+    private static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS =
+            OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS != null
+                    ? OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS : 0;
+
+    private SuggestionsInfoCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    /**
+     * Returns the flag value of the attributes of the suggestions that can be obtained by
+     * {@link SuggestionsInfo#getSuggestionsAttributes()}: this tells that the text service thinks
+     * the result suggestions include highly recommended ones.
+     */
+    public static int getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() {
+        return RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
deleted file mode 100644
index 2fb8b87..0000000
--- a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.content.Context;
-import android.os.Vibrator;
-
-import java.lang.reflect.Method;
-
-public class VibratorCompatWrapper {
-    private static final Method METHOD_hasVibrator = CompatUtils.getMethod(Vibrator.class,
-            "hasVibrator");
-
-    private static final VibratorCompatWrapper sInstance = new VibratorCompatWrapper();
-    private Vibrator mVibrator;
-
-    private VibratorCompatWrapper() {
-    }
-
-    public static VibratorCompatWrapper getInstance(Context context) {
-        if (sInstance.mVibrator == null) {
-            sInstance.mVibrator =
-                    (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
-        }
-        return sInstance;
-    }
-
-    public boolean hasVibrator() {
-        if (mVibrator == null)
-            return false;
-        return (Boolean) CompatUtils.invoke(mVibrator, true, METHOD_hasVibrator);
-    }
-
-    public void vibrate(long milliseconds) {
-        mVibrator.vibrate(milliseconds);
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java b/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java
deleted file mode 100644
index 290e6b5..0000000
--- a/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated;
-
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.deprecated.languageswitcher.LanguageSwitcher;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.Settings;
-
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-
-import java.util.Locale;
-
-// This class is used only when the IME doesn't use method.xml for language switching.
-public class LanguageSwitcherProxy implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final LanguageSwitcherProxy sInstance = new LanguageSwitcherProxy();
-    private LatinIME mService;
-    private LanguageSwitcher mLanguageSwitcher;
-    private SharedPreferences mPrefs;
-
-    public static LanguageSwitcherProxy getInstance() {
-        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return null;
-        return sInstance;
-    }
-
-    public static void init(LatinIME service, SharedPreferences prefs) {
-        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
-        final Configuration conf = service.getResources().getConfiguration();
-        sInstance.mLanguageSwitcher = new LanguageSwitcher(service);
-        sInstance.mLanguageSwitcher.loadLocales(prefs, conf.locale);
-        sInstance.mPrefs = prefs;
-        sInstance.mService = service;
-        prefs.registerOnSharedPreferenceChangeListener(sInstance);
-    }
-
-    public static void onConfigurationChanged(Configuration conf) {
-        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
-        sInstance.mLanguageSwitcher.onConfigurationChanged(conf, sInstance.mPrefs);
-    }
-
-    public static void loadSettings() {
-        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
-        sInstance.mLanguageSwitcher.loadLocales(sInstance.mPrefs, null);
-    }
-
-    public int getLocaleCount() {
-        return mLanguageSwitcher.getLocaleCount();
-    }
-
-    public String[] getEnabledLanguages(boolean allowImplicitlySelectedLanguages) {
-        return mLanguageSwitcher.getEnabledLanguages(allowImplicitlySelectedLanguages);
-    }
-
-    public Locale getInputLocale() {
-        return mLanguageSwitcher.getInputLocale();
-    }
-
-    public void setLocale(String localeStr) {
-        mLanguageSwitcher.setLocale(localeStr);
-        mLanguageSwitcher.persist(mPrefs);
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        // PREF_SELECTED_LANGUAGES: enabled input subtypes
-        // PREF_INPUT_LANGUAGE: current input subtype
-        if (key.equals(Settings.PREF_SELECTED_LANGUAGES)
-                || key.equals(Settings.PREF_INPUT_LANGUAGE)) {
-            mLanguageSwitcher.loadLocales(prefs, null);
-            if (mService != null) {
-                mService.onRefreshKeyboard();
-            }
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
deleted file mode 100644
index 3f8c2ef..0000000
--- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
+++ /dev/null
@@ -1,880 +0,0 @@
-/*
- * 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.deprecated;
-
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
-import com.android.inputmethod.compat.SharedPreferencesCompat;
-import com.android.inputmethod.deprecated.voice.FieldContext;
-import com.android.inputmethod.deprecated.voice.Hints;
-import com.android.inputmethod.deprecated.voice.SettingsUtil;
-import com.android.inputmethod.deprecated.voice.VoiceInput;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.latin.EditingUtils;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.LatinIME.UIHandler;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.Utils;
-
-import android.app.AlertDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.IBinder;
-import android.preference.PreferenceManager;
-import android.provider.Browser;
-import android.speech.SpeechRecognizer;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.text.style.URLSpan;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class VoiceProxy implements VoiceInput.UiListener {
-    private static final VoiceProxy sInstance = new VoiceProxy();
-
-    public static final boolean VOICE_INSTALLED =
-            !InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED;
-    private static final boolean ENABLE_VOICE_BUTTON = true;
-    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
-    // first-run warning dialog or not).
-    private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
-    // Whether or not the user has used voice input from an unsupported locale UI before.
-    // For example, the user has a Chinese UI but activates voice input.
-    private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
-            "has_used_voice_input_unsupported_locale";
-    private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
-    // TODO: Adjusted on phones for now
-    private static final int RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP = 244;
-
-    private static final String TAG = VoiceProxy.class.getSimpleName();
-    private static final boolean DEBUG = LatinImeLogger.sDBG;
-
-    private boolean mAfterVoiceInput;
-    private boolean mHasUsedVoiceInput;
-    private boolean mHasUsedVoiceInputUnsupportedLocale;
-    private boolean mImmediatelyAfterVoiceInput;
-    private boolean mIsShowingHint;
-    private boolean mLocaleSupportedForVoiceInput;
-    private boolean mPasswordText;
-    private boolean mRecognizing;
-    private boolean mShowingVoiceSuggestions;
-    private boolean mVoiceButtonEnabled;
-    private boolean mVoiceButtonOnPrimary;
-    private boolean mVoiceInputHighlighted;
-
-    private int mMinimumVoiceRecognitionViewHeightPixel;
-    private InputMethodManagerCompatWrapper mImm;
-    private LatinIME mService;
-    private AlertDialog mVoiceWarningDialog;
-    private VoiceInput mVoiceInput;
-    private final VoiceResults mVoiceResults = new VoiceResults();
-    private Hints mHints;
-    private UIHandler mHandler;
-    private SubtypeSwitcher mSubtypeSwitcher;
-
-    // For each word, a list of potential replacements, usually from voice.
-    private final Map<String, List<CharSequence>> mWordToSuggestions =
-            new HashMap<String, List<CharSequence>>();
-
-    public static VoiceProxy init(LatinIME context, SharedPreferences prefs, UIHandler h) {
-        sInstance.initInternal(context, prefs, h);
-        return sInstance;
-    }
-
-    public static VoiceProxy getInstance() {
-        return sInstance;
-    }
-
-    private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        mService = service;
-        mHandler = h;
-        mMinimumVoiceRecognitionViewHeightPixel = Utils.dipToPixel(
-                Utils.getDipScale(service), RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP);
-        mImm = InputMethodManagerCompatWrapper.getInstance();
-        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-        mVoiceInput = new VoiceInput(service, this);
-        mHints = new Hints(service, prefs, new Hints.Display() {
-            @Override
-            public void showHint(int viewResource) {
-                View view = LayoutInflater.from(mService).inflate(viewResource, null);
-                mIsShowingHint = true;
-            }
-        });
-    }
-
-    private VoiceProxy() {
-        // Intentional empty constructor for singleton.
-    }
-
-    public void resetVoiceStates(boolean isPasswordText) {
-        mAfterVoiceInput = false;
-        mImmediatelyAfterVoiceInput = false;
-        mShowingVoiceSuggestions = false;
-        mVoiceInputHighlighted = false;
-        mPasswordText = isPasswordText;
-    }
-
-    public void flushVoiceInputLogs(boolean configurationChanged) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (!configurationChanged) {
-            if (mAfterVoiceInput) {
-                mVoiceInput.flushAllTextModificationCounters();
-                mVoiceInput.logInputEnded();
-            }
-            mVoiceInput.flushLogs();
-            mVoiceInput.cancel();
-        }
-    }
-
-    public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion,
-            String wordSeparators) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mAfterVoiceInput && mShowingVoiceSuggestions) {
-            mVoiceInput.flushAllTextModificationCounters();
-            // send this intent AFTER logging any prior aggregated edits.
-            mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
-                    wordSeparators, mService.getCurrentInputConnection());
-        }
-    }
-
-    private void showVoiceWarningDialog(final boolean swipe, IBinder token) {
-        if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
-            return;
-        }
-        AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService);
-        builder.setCancelable(true);
-        builder.setIcon(R.drawable.ic_mic_dialog);
-        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int whichButton) {
-                mVoiceInput.logKeyboardWarningDialogOk();
-                reallyStartListening(swipe);
-            }
-        });
-        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int whichButton) {
-                mVoiceInput.logKeyboardWarningDialogCancel();
-                switchToLastInputMethod();
-            }
-        });
-        // When the dialog is dismissed by user's cancellation, switch back to the last input method
-        builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
-            @Override
-            public void onCancel(DialogInterface arg0) {
-                mVoiceInput.logKeyboardWarningDialogCancel();
-                switchToLastInputMethod();
-            }
-        });
-
-        final CharSequence message;
-        if (mLocaleSupportedForVoiceInput) {
-            message = TextUtils.concat(
-                    mService.getText(R.string.voice_warning_may_not_understand), "\n\n",
-                            mService.getText(R.string.voice_warning_how_to_turn_off));
-        } else {
-            message = TextUtils.concat(
-                    mService.getText(R.string.voice_warning_locale_not_supported), "\n\n",
-                            mService.getText(R.string.voice_warning_may_not_understand), "\n\n",
-                                    mService.getText(R.string.voice_warning_how_to_turn_off));
-        }
-        builder.setMessage(message);
-        builder.setTitle(R.string.voice_warning_title);
-        mVoiceWarningDialog = builder.create();
-        final Window window = mVoiceWarningDialog.getWindow();
-        final WindowManager.LayoutParams lp = window.getAttributes();
-        lp.token = token;
-        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-        window.setAttributes(lp);
-        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-        mVoiceInput.logKeyboardWarningDialogShown();
-        mVoiceWarningDialog.show();
-    }
-
-    private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder {
-        private AlertDialog mAlertDialog;
-
-        public UrlLinkAlertDialogBuilder(Context context) {
-            super(context);
-        }
-
-        @Override
-        public AlertDialog.Builder setMessage(CharSequence message) {
-            return super.setMessage(replaceURLSpan(message));
-        }
-
-        private Spanned replaceURLSpan(CharSequence message) {
-            // Replace all spans with the custom span
-            final SpannableStringBuilder ssb = new SpannableStringBuilder(message);
-            for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) {
-                int spanStart = ssb.getSpanStart(span);
-                int spanEnd = ssb.getSpanEnd(span);
-                int spanFlags = ssb.getSpanFlags(span);
-                ssb.removeSpan(span);
-                ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags);
-            }
-            return ssb;
-        }
-
-        @Override
-        public AlertDialog create() {
-            final AlertDialog dialog = super.create();
-
-            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
-                @Override
-                public void onShow(DialogInterface dialogInterface) {
-                    // Make URL in the dialog message click-able.
-                    TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message);
-                    if (textView != null) {
-                        textView.setMovementMethod(LinkMovementMethod.getInstance());
-                    }
-                }
-            });
-            mAlertDialog = dialog;
-            return dialog;
-        }
-
-        class ClickableSpan extends URLSpan {
-            public ClickableSpan(String url) {
-                super(url);
-            }
-
-            @Override
-            public void onClick(View widget) {
-                Uri uri = Uri.parse(getURL());
-                Context context = widget.getContext();
-                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-                // Add this flag to start an activity from service
-                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
-                // Dismiss the warning dialog and go back to the previous IME.
-                // TODO: If we can find a way to bring the new activity to front while keeping
-                // the warning dialog, we don't need to dismiss it here.
-                mAlertDialog.cancel();
-                context.startActivity(intent);
-            }
-        }
-    }
-
-    public void showPunctuationHintIfNecessary() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        InputConnection ic = mService.getCurrentInputConnection();
-        if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
-            if (mHints.showPunctuationHintIfNecessary(ic)) {
-                mVoiceInput.logPunctuationHintDisplayed();
-            }
-        }
-        mImmediatelyAfterVoiceInput = false;
-    }
-
-    public void hideVoiceWindow(boolean configurationChanging) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (!configurationChanging) {
-            if (mAfterVoiceInput)
-                mVoiceInput.logInputEnded();
-            if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
-                mVoiceInput.logKeyboardWarningDialogDismissed();
-                mVoiceWarningDialog.dismiss();
-                mVoiceWarningDialog = null;
-            }
-            if (VOICE_INSTALLED & mRecognizing) {
-                mVoiceInput.cancel();
-            }
-        }
-        mWordToSuggestions.clear();
-    }
-
-    public void setCursorAndSelection(int newSelEnd, int newSelStart) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mAfterVoiceInput) {
-            mVoiceInput.setCursorPos(newSelEnd);
-            mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
-        }
-    }
-
-    public void setVoiceInputHighlighted(boolean b) {
-        mVoiceInputHighlighted = b;
-    }
-
-    public void setShowingVoiceSuggestions(boolean b) {
-        mShowingVoiceSuggestions = b;
-    }
-
-    public boolean isVoiceButtonEnabled() {
-        return mVoiceButtonEnabled;
-    }
-
-    public boolean isVoiceButtonOnPrimary() {
-        return mVoiceButtonOnPrimary;
-    }
-
-    public boolean isVoiceInputHighlighted() {
-        return mVoiceInputHighlighted;
-    }
-
-    public boolean isRecognizing() {
-        return mRecognizing;
-    }
-
-    public boolean needsToShowWarningDialog() {
-        return !mHasUsedVoiceInput
-                || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale);
-    }
-
-    public boolean getAndResetIsShowingHint() {
-        boolean ret = mIsShowingHint;
-        mIsShowingHint = false;
-        return ret;
-    }
-
-    private void revertVoiceInput() {
-        InputConnection ic = mService.getCurrentInputConnection();
-        if (ic != null) ic.commitText("", 1);
-        mService.updateSuggestions();
-        mVoiceInputHighlighted = false;
-    }
-
-    public void commitVoiceInput() {
-        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
-            InputConnection ic = mService.getCurrentInputConnection();
-            if (ic != null) ic.finishComposingText();
-            mService.updateSuggestions();
-            mVoiceInputHighlighted = false;
-        }
-    }
-
-    public boolean logAndRevertVoiceInput() {
-        if (!VOICE_INSTALLED) {
-            return false;
-        }
-        if (mVoiceInputHighlighted) {
-            mVoiceInput.incrementTextModificationDeleteCount(
-                    mVoiceResults.candidates.get(0).toString().length());
-            revertVoiceInput();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mShowingVoiceSuggestions) {
-            // Retain the replaced word in the alternatives array.
-            String wordToBeReplaced = EditingUtils.getWordAtCursor(
-                    mService.getCurrentInputConnection(), wordSeparators);
-            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);
-            }
-        }
-    }
-
-    /**
-     * Tries to apply any voice alternatives for the word if this was a spoken word and
-     * there are voice alternatives.
-     * @param touching The word that the cursor is touching, with position information
-     * @return true if an alternative was found, false otherwise.
-     */
-    public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) {
-        if (!VOICE_INSTALLED) {
-            return false;
-        }
-        // Search for result in spoken word alternatives
-        String selectedWord = touching.mWord.toString().trim();
-        if (!mWordToSuggestions.containsKey(selectedWord)) {
-            selectedWord = selectedWord.toLowerCase();
-        }
-        if (mWordToSuggestions.containsKey(selectedWord)) {
-            mShowingVoiceSuggestions = true;
-            List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
-            SuggestedWords.Builder builder = new SuggestedWords.Builder();
-            // If the first letter of touching is capitalized, make all the suggestions
-            // start with a capital letter.
-            if (Character.isUpperCase(touching.mWord.charAt(0))) {
-                for (CharSequence word : suggestions) {
-                    String str = word.toString();
-                    word = Character.toUpperCase(str.charAt(0)) + str.substring(1);
-                    builder.addWord(word);
-                }
-            } else {
-                builder.addWords(suggestions, null);
-            }
-            builder.setTypedWordValid(true).setHasMinimalSuggestion(true);
-            mService.setSuggestions(builder.build());
-//            mService.setCandidatesViewShown(true);
-            return true;
-        }
-        return false;
-    }
-
-    public void handleBackspace() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mAfterVoiceInput) {
-            // Don't log delete if the user is pressing delete at
-            // the beginning of the text box (hence not deleting anything)
-            if (mVoiceInput.getCursorPos() > 0) {
-                // If anything was selected before the delete was pressed, increment the
-                // delete count by the length of the selection
-                int deleteLen  =  mVoiceInput.getSelectionSpan() > 0 ?
-                        mVoiceInput.getSelectionSpan() : 1;
-                mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
-            }
-        }
-    }
-
-    public void handleCharacter() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        commitVoiceInput();
-        if (mAfterVoiceInput) {
-            // Assume input length is 1. This assumption fails for smiley face insertions.
-            mVoiceInput.incrementTextModificationInsertCount(1);
-        }
-    }
-
-    public void handleSeparator() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        commitVoiceInput();
-        if (mAfterVoiceInput){
-            // Assume input length is 1. This assumption fails for smiley face insertions.
-            mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
-        }
-    }
-
-    public void handleClose() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mRecognizing) {
-            mVoiceInput.cancel();
-        }
-    }
-
-
-    public void handleVoiceResults(boolean capitalizeFirstWord) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        mAfterVoiceInput = true;
-        mImmediatelyAfterVoiceInput = true;
-
-        InputConnection ic = mService.getCurrentInputConnection();
-        if (!mService.isFullscreenMode()) {
-            // Start listening for updates to the text from typing, etc.
-            if (ic != null) {
-                ExtractedTextRequest req = new ExtractedTextRequest();
-                ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
-            }
-        }
-        mService.vibrate();
-
-        final List<CharSequence> nBest = new ArrayList<CharSequence>();
-        for (String c : mVoiceResults.candidates) {
-            if (capitalizeFirstWord) {
-                c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
-            }
-            nBest.add(c);
-        }
-        if (nBest.size() == 0) {
-            return;
-        }
-        String bestResult = nBest.get(0).toString();
-        mVoiceInput.logVoiceInputDelivered(bestResult.length());
-        mHints.registerVoiceResult(bestResult);
-
-        if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
-        mService.commitTyped(ic);
-        EditingUtils.appendText(ic, bestResult);
-        if (ic != null) ic.endBatchEdit();
-
-        mVoiceInputHighlighted = true;
-        mWordToSuggestions.putAll(mVoiceResults.alternatives);
-        onCancelVoice();
-    }
-
-    public void switchToRecognitionStatusView(final Configuration configuration) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-//                mService.setCandidatesViewShown(false);
-                mRecognizing = true;
-                mVoiceInput.newView();
-                View v = mVoiceInput.getView();
-
-                ViewParent p = v.getParent();
-                if (p != null && p instanceof ViewGroup) {
-                    ((ViewGroup) p).removeView(v);
-                }
-
-                View keyboardView = KeyboardSwitcher.getInstance().getKeyboardView();
-
-                // The full height of the keyboard is difficult to calculate
-                // as the dimension is expressed in "mm" and not in "pixel"
-                // As we add mm, we don't know how the rounding is going to work
-                // thus we may end up with few pixels extra (or less).
-                if (keyboardView != null) {
-                    View popupLayout = v.findViewById(R.id.popup_layout);
-                    final int displayHeight =
-                            mService.getResources().getDisplayMetrics().heightPixels;
-                    final int currentHeight = popupLayout.getLayoutParams().height;
-                    final int keyboardHeight = keyboardView.getHeight();
-                    if (mMinimumVoiceRecognitionViewHeightPixel > keyboardHeight
-                            || mMinimumVoiceRecognitionViewHeightPixel > currentHeight) {
-                        popupLayout.getLayoutParams().height =
-                            mMinimumVoiceRecognitionViewHeightPixel;
-                    } else if (keyboardHeight > currentHeight || keyboardHeight
-                            > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) {
-                        popupLayout.getLayoutParams().height = keyboardHeight;
-                    }
-                }
-                mService.setInputView(v);
-                mService.updateInputViewShown();
-
-                if (configuration != null) {
-                    mVoiceInput.onConfigurationChanged(configuration);
-                }
-        }});
-    }
-
-    private void switchToLastInputMethod() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
-        new AsyncTask<Void, Void, Boolean>() {
-            @Override
-            protected Boolean doInBackground(Void... params) {
-                return mImm.switchToLastInputMethod(token);
-            }
-
-            @Override
-            protected void onPostExecute(Boolean result) {
-                // Calls in this method need to be done in the same thread as the thread which
-                // called switchToLastInputMethod()
-                if (!result) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Couldn't switch back to last IME.");
-                    }
-                    // Because the current IME and subtype failed to switch to any other IME and
-                    // subtype by switchToLastInputMethod, the current IME and subtype should keep
-                    // being LatinIME and voice subtype in the next time. And for re-showing voice
-                    // mode, the state of voice input should be reset and the voice view should be
-                    // hidden.
-                    mVoiceInput.reset();
-                    mService.requestHideSelf(0);
-                } else {
-                    // Notify an event that the current subtype was changed. This event will be
-                    // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
-                    // when the API level is 10 or previous.
-                    mService.notifyOnCurrentInputMethodSubtypeChanged(null);
-                }
-            }
-        }.execute();
-    }
-
-    private void reallyStartListening(boolean swipe) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (!mHasUsedVoiceInput) {
-            // The user has started a voice input, so remember that in the
-            // future (so we don't show the warning dialog after the first run).
-            SharedPreferences.Editor editor =
-                    PreferenceManager.getDefaultSharedPreferences(mService).edit();
-            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
-            SharedPreferencesCompat.apply(editor);
-            mHasUsedVoiceInput = true;
-        }
-
-        if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
-            // The user has started a voice input from an unsupported locale, so remember that
-            // in the future (so we don't show the warning dialog the next time they do this).
-            SharedPreferences.Editor editor =
-                    PreferenceManager.getDefaultSharedPreferences(mService).edit();
-            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
-            SharedPreferencesCompat.apply(editor);
-            mHasUsedVoiceInputUnsupportedLocale = true;
-        }
-
-        // Clear N-best suggestions
-        mService.clearSuggestions();
-
-        FieldContext context = makeFieldContext();
-        mVoiceInput.startListening(context, swipe);
-        switchToRecognitionStatusView(null);
-    }
-
-    public void startListening(final boolean swipe, IBinder token) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        // TODO: remove swipe which is no longer used.
-        if (needsToShowWarningDialog()) {
-            // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
-            showVoiceWarningDialog(swipe, token);
-        } else {
-            reallyStartListening(swipe);
-        }
-    }
-
-    private boolean fieldCanDoVoice(FieldContext fieldContext) {
-        return !mPasswordText
-                && mVoiceInput != null
-                && !mVoiceInput.isBlacklistedField(fieldContext);
-    }
-
-    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
-        @SuppressWarnings("deprecation")
-        final boolean noMic = Utils.inPrivateImeOptions(null,
-                LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute)
-                || Utils.inPrivateImeOptions(mService.getPackageName(),
-                        LatinIME.IME_OPTION_NO_MICROPHONE, attribute);
-        return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
-                && SpeechRecognizer.isRecognitionAvailable(mService);
-    }
-
-    public static boolean isRecognitionAvailable(Context context) {
-        return SpeechRecognizer.isRecognitionAvailable(context);
-    }
-
-    public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
-        mHasUsedVoiceInputUnsupportedLocale =
-                sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
-
-        mLocaleSupportedForVoiceInput = SubtypeSwitcher.isVoiceSupported(
-                mService, SubtypeSwitcher.getInstance().getInputLocaleStr());
-
-        final String voiceMode = sp.getString(PREF_VOICE_MODE,
-                mService.getString(R.string.voice_mode_main));
-        mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off))
-                && shouldShowVoiceButton(makeFieldContext(), attribute);
-        mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main));
-    }
-
-    public void destroy() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mVoiceInput != null) {
-            mVoiceInput.destroy();
-        }
-    }
-
-    public void onStartInputView(IBinder keyboardViewToken) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached.
-        IBinder windowToken = keyboardViewToken != null ? keyboardViewToken
-                : mVoiceInput.getView().getWindowToken();
-        // If IME is in voice mode, but still needs to show the voice warning dialog,
-        // keep showing the warning.
-        if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) {
-            // Close keyboard view if it is been shown.
-            if (KeyboardSwitcher.getInstance().isInputViewShown())
-                KeyboardSwitcher.getInstance().getKeyboardView().purgeKeyboardAndClosing();
-            startListening(false, windowToken);
-        }
-        // If we have no token, onAttachedToWindow will take care of showing dialog and start
-        // listening.
-    }
-
-    public void onAttachedToWindow() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        // After onAttachedToWindow, we can show the voice warning dialog. See startListening()
-        // above.
-        VoiceInputWrapper.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher);
-    }
-
-    public void onConfigurationChanged(Configuration configuration) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mRecognizing) {
-            switchToRecognitionStatusView(configuration);
-        }
-    }
-
-    @Override
-    public void onCancelVoice() {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (mRecognizing) {
-            if (mSubtypeSwitcher.isVoiceMode()) {
-                // If voice mode is being canceled within LatinIME (i.e. time-out or user
-                // cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's
-                // still in voice mode. LatinIME needs to call switchToLastInputMethod().
-                // Note that onCancelVoice() will be called again from SubtypeSwitcher.
-                switchToLastInputMethod();
-            } else if (mSubtypeSwitcher.isKeyboardMode()) {
-                // If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or
-                // as a result of switchToLastInputMethod() etc.),
-                // onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know
-                // that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice().
-                mRecognizing = false;
-                mService.switchToKeyboardView();
-            }
-        }
-    }
-
-    @Override
-    public void onVoiceResults(List<String> candidates,
-            Map<String, List<CharSequence>> alternatives) {
-        if (!VOICE_INSTALLED) {
-            return;
-        }
-        if (!mRecognizing) {
-            return;
-        }
-        mVoiceResults.candidates = candidates;
-        mVoiceResults.alternatives = alternatives;
-        mHandler.updateVoiceResults();
-    }
-
-    private FieldContext makeFieldContext() {
-        SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
-        return new FieldContext(mService.getCurrentInputConnection(),
-                mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
-                switcher.getEnabledLanguages());
-    }
-
-    // TODO: make this private (proguard issue)
-    public static class VoiceResults {
-        List<String> candidates;
-        Map<String, List<CharSequence>> alternatives;
-    }
-
-    public static class VoiceInputWrapper {
-        private static final VoiceInputWrapper sInputWrapperInstance = new VoiceInputWrapper();
-        private VoiceInput mVoiceInput;
-        public static VoiceInputWrapper getInstance() {
-            return sInputWrapperInstance;
-        }
-        private void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) {
-            if (!VOICE_INSTALLED) {
-                return;
-            }
-            if (mVoiceInput == null && voiceInput != null) {
-                mVoiceInput = voiceInput;
-            }
-            switcher.setVoiceInputWrapper(this);
-        }
-
-        private VoiceInputWrapper() {
-        }
-
-        public void cancel() {
-            if (!VOICE_INSTALLED) {
-                return;
-            }
-            if (mVoiceInput != null) mVoiceInput.cancel();
-        }
-
-        public void reset() {
-            if (!VOICE_INSTALLED) {
-                return;
-            }
-            if (mVoiceInput != null) mVoiceInput.reset();
-        }
-    }
-
-    // A list of locales which are supported by default for voice input, unless we get a
-    // different list from Gservices.
-    private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
-            "en " +
-            "en_US " +
-            "en_GB " +
-            "en_AU " +
-            "en_CA " +
-            "en_IE " +
-            "en_IN " +
-            "en_NZ " +
-            "en_SG " +
-            "en_ZA ";
-
-    public static String getSupportedLocalesString (ContentResolver resolver) {
-        return SettingsUtil.getSettingsString(
-                resolver,
-                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
-                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java
deleted file mode 100644
index 488390f..0000000
--- a/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.deprecated.compat;
-
-import com.android.common.userhappiness.UserHappinessSignals;
-import com.android.inputmethod.compat.CompatUtils;
-
-import java.lang.reflect.Method;
-
-public class VoiceInputLoggerCompatUtils {
-    public static final String EXTRA_TEXT_REPLACED_LENGTH = "length";
-    public static final String EXTRA_BEFORE_N_BEST_CHOOSE = "before";
-    public static final String EXTRA_AFTER_N_BEST_CHOOSE = "after";
-    private static final Method METHOD_UserHappinessSignals_setHasVoiceLoggingInfo =
-            CompatUtils.getMethod(UserHappinessSignals.class, "setHasVoiceLoggingInfo",
-                    boolean.class);
-
-    public static void setHasVoiceLoggingInfoCompat(boolean hasLoggingInfo) {
-        CompatUtils.invoke(null, null, METHOD_UserHappinessSignals_setHasVoiceLoggingInfo,
-                hasLoggingInfo);
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
deleted file mode 100644
index dbe7aec..0000000
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2008-2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.languageswitcher;
-
-import com.android.inputmethod.compat.SharedPreferencesCompat;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
-import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.Utils;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import java.io.IOException;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-public class InputLanguageSelection extends PreferenceActivity {
-
-    private SharedPreferences mPrefs;
-    private String mSelectedLanguages;
-    private HashMap<CheckBoxPreference, Locale> mLocaleMap =
-            new HashMap<CheckBoxPreference, Locale>();
-
-    private static class LocaleEntry implements Comparable<Object> {
-        private static Collator sCollator = Collator.getInstance();
-
-        private String mLabel;
-        public final Locale mLocale;
-
-        public LocaleEntry(String label, Locale locale) {
-            this.mLabel = label;
-            this.mLocale = locale;
-        }
-
-        @Override
-        public String toString() {
-            return this.mLabel;
-        }
-
-        @Override
-        public int compareTo(Object o) {
-            return sCollator.compare(this.mLabel, ((LocaleEntry) o).mLabel);
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        addPreferencesFromResource(R.xml.language_prefs);
-        // Get the settings preferences
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
-        mSelectedLanguages = mPrefs.getString(Settings.PREF_SELECTED_LANGUAGES, "");
-        String[] languageList = mSelectedLanguages.split(",");
-        ArrayList<LocaleEntry> availableLanguages = getUniqueLocales();
-        PreferenceGroup parent = getPreferenceScreen();
-        final HashMap<Long, LocaleEntry> dictionaryIdLocaleMap = new HashMap<Long, LocaleEntry>();
-        final TreeMap<LocaleEntry, Boolean> localeHasDictionaryMap =
-                new TreeMap<LocaleEntry, Boolean>();
-        for (int i = 0; i < availableLanguages.size(); i++) {
-            LocaleEntry loc = availableLanguages.get(i);
-            Locale locale = loc.mLocale;
-            final Pair<Long, Boolean> hasDictionaryOrLayout = hasDictionaryOrLayout(locale);
-            final Long dictionaryId = hasDictionaryOrLayout.first;
-            final boolean hasLayout = hasDictionaryOrLayout.second;
-            final boolean hasDictionary = dictionaryId != null;
-            // Add this locale to the supported list if:
-            // 1) this locale has a layout/ 2) this locale has a dictionary
-            // If some locales have no layout but have a same dictionary, the shortest locale
-            // will be added to the supported list.
-            if (!hasLayout && !hasDictionary) {
-                continue;
-            }
-            if (hasLayout) {
-                localeHasDictionaryMap.put(loc, hasDictionary);
-            }
-            if (!hasDictionary) {
-                continue;
-            }
-            if (dictionaryIdLocaleMap.containsKey(dictionaryId)) {
-                final String newLocale = locale.toString();
-                final String oldLocale =
-                        dictionaryIdLocaleMap.get(dictionaryId).mLocale.toString();
-                // Check if this locale is more appropriate to be the candidate of the input locale.
-                if (oldLocale.length() <= newLocale.length() && !hasLayout) {
-                    // Don't add this new locale to the map<dictionary id, locale> if:
-                    // 1) the new locale's name is longer than the existing one, and
-                    // 2) the new locale doesn't have its layout
-                    continue;
-                }
-            }
-            dictionaryIdLocaleMap.put(dictionaryId, loc);
-        }
-
-        for (LocaleEntry localeEntry : dictionaryIdLocaleMap.values()) {
-            if (!localeHasDictionaryMap.containsKey(localeEntry)) {
-                localeHasDictionaryMap.put(localeEntry, true);
-            }
-        }
-
-        for (Entry<LocaleEntry, Boolean> entry : localeHasDictionaryMap.entrySet()) {
-            final LocaleEntry localeEntry = entry.getKey();
-            final Locale locale = localeEntry.mLocale;
-            final Boolean hasDictionary = entry.getValue();
-            CheckBoxPreference pref = new CheckBoxPreference(this);
-            pref.setTitle(localeEntry.mLabel);
-            boolean checked = isLocaleIn(locale, languageList);
-            pref.setChecked(checked);
-            if (hasDictionary) {
-                pref.setSummary(R.string.has_dictionary);
-            }
-            mLocaleMap.put(pref, locale);
-            parent.addPreference(pref);
-        }
-    }
-
-    private boolean isLocaleIn(Locale locale, String[] list) {
-        String lang = get5Code(locale);
-        for (int i = 0; i < list.length; i++) {
-            if (lang.equalsIgnoreCase(list[i])) return true;
-        }
-        return false;
-    }
-
-    private Pair<Long, Boolean> hasDictionaryOrLayout(Locale locale) {
-        if (locale == null) return new Pair<Long, Boolean>(null, false);
-        final Resources res = getResources();
-        final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
-        final Long dictionaryId = DictionaryFactory.getDictionaryId(this, locale);
-        boolean hasLayout = false;
-
-        try {
-            final String localeStr = locale.toString();
-            final String[] layoutCountryCodes = KeyboardBuilder.parseKeyboardLocale(
-                    this, R.xml.kbd_qwerty).split(",", -1);
-            if (!TextUtils.isEmpty(localeStr) && layoutCountryCodes.length > 0) {
-                for (String s : layoutCountryCodes) {
-                    if (s.equals(localeStr)) {
-                        hasLayout = true;
-                        break;
-                    }
-                }
-            }
-        } catch (XmlPullParserException e) {
-        } catch (IOException e) {
-        }
-        LocaleUtils.setSystemLocale(res, saveLocale);
-        return new Pair<Long, Boolean>(dictionaryId, hasLayout);
-    }
-
-    private String get5Code(Locale locale) {
-        String country = locale.getCountry();
-        return locale.getLanguage()
-                + (TextUtils.isEmpty(country) ? "" : "_" + country);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        // Save the selected languages
-        String checkedLanguages = "";
-        PreferenceGroup parent = getPreferenceScreen();
-        int count = parent.getPreferenceCount();
-        for (int i = 0; i < count; i++) {
-            CheckBoxPreference pref = (CheckBoxPreference) parent.getPreference(i);
-            if (pref.isChecked()) {
-                checkedLanguages += get5Code(mLocaleMap.get(pref)) + ",";
-            }
-        }
-        if (checkedLanguages.length() < 1) checkedLanguages = null; // Save null
-        Editor editor = mPrefs.edit();
-        editor.putString(Settings.PREF_SELECTED_LANGUAGES, checkedLanguages);
-        SharedPreferencesCompat.apply(editor);
-    }
-
-    public ArrayList<LocaleEntry> getUniqueLocales() {
-        String[] locales = getAssets().getLocales();
-        Arrays.sort(locales);
-        ArrayList<LocaleEntry> uniqueLocales = new ArrayList<LocaleEntry>();
-
-        final int origSize = locales.length;
-        LocaleEntry[] preprocess = new LocaleEntry[origSize];
-        int finalSize = 0;
-        for (int i = 0 ; i < origSize; i++ ) {
-            String s = locales[i];
-            int len = s.length();
-            String language = "";
-            String country = "";
-            if (len == 5) {
-                language = s.substring(0, 2);
-                country = s.substring(3, 5);
-            } else if (len < 5) {
-                language = s;
-            }
-            Locale l = new Locale(language, country);
-
-            // Exclude languages that are not relevant to LatinIME
-            if (TextUtils.isEmpty(language)) {
-                continue;
-            }
-
-            if (finalSize == 0) {
-                preprocess[finalSize++] =
-                        new LocaleEntry(Utils.getFullDisplayName(l, false), l);
-            } else {
-                if (s.equals("zz_ZZ")) {
-                    // ignore this locale
-                } else {
-                    final String displayName = Utils.getFullDisplayName(l, false);
-                    preprocess[finalSize++] = new LocaleEntry(displayName, l);
-                }
-            }
-        }
-        for (int i = 0; i < finalSize ; i++) {
-            uniqueLocales.add(preprocess[i]);
-        }
-        return uniqueLocales;
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
deleted file mode 100644
index 7e2627c..0000000
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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.deprecated.languageswitcher;
-
-import com.android.inputmethod.compat.SharedPreferencesCompat;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.Settings;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.res.Configuration;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-/**
- * Keeps track of list of selected input languages and the current
- * input language that the user has selected.
- */
-public class LanguageSwitcher {
-    private static final String TAG = LanguageSwitcher.class.getSimpleName();
-
-    @SuppressWarnings("unused")
-    private static final String KEYBOARD_MODE = "keyboard";
-    private static final String[] EMPTY_STIRNG_ARRAY = new String[0];
-
-    private final ArrayList<Locale> mLocales = new ArrayList<Locale>();
-    private final LatinIME mIme;
-    private String[] mSelectedLanguageArray = EMPTY_STIRNG_ARRAY;
-    private String   mSelectedLanguages;
-    private int      mCurrentIndex = 0;
-    private String   mDefaultInputLanguage;
-    private Locale   mDefaultInputLocale;
-    private Locale   mSystemLocale;
-
-    public LanguageSwitcher(LatinIME ime) {
-        mIme = ime;
-    }
-
-    public int getLocaleCount() {
-        return mLocales.size();
-    }
-
-    public void onConfigurationChanged(Configuration conf, SharedPreferences prefs) {
-        final Locale newLocale = conf.locale;
-        if (!getSystemLocale().toString().equals(newLocale.toString())) {
-            loadLocales(prefs, newLocale);
-        }
-    }
-
-    /**
-     * Loads the currently selected input languages from shared preferences.
-     * @param sp shared preference for getting the current input language and enabled languages
-     * @param systemLocale the current system locale, stored for changing the current input language
-     * based on the system current system locale.
-     * @return whether there was any change
-     */
-    public boolean loadLocales(SharedPreferences sp, Locale systemLocale) {
-        if (LatinImeLogger.sDBG) {
-            Log.d(TAG, "load locales");
-        }
-        if (systemLocale != null) {
-            setSystemLocale(systemLocale);
-        }
-        String selectedLanguages = sp.getString(Settings.PREF_SELECTED_LANGUAGES, null);
-        String currentLanguage   = sp.getString(Settings.PREF_INPUT_LANGUAGE, null);
-        if (TextUtils.isEmpty(selectedLanguages)) {
-            mSelectedLanguageArray = EMPTY_STIRNG_ARRAY;
-            mSelectedLanguages = null;
-            loadDefaults();
-            if (mLocales.size() == 0) {
-                return false;
-            }
-            mLocales.clear();
-            return true;
-        }
-        if (selectedLanguages.equals(mSelectedLanguages)) {
-            return false;
-        }
-        mSelectedLanguageArray = selectedLanguages.split(",");
-        mSelectedLanguages = selectedLanguages; // Cache it for comparison later
-        constructLocales();
-        mCurrentIndex = 0;
-        if (currentLanguage != null) {
-            // Find the index
-            mCurrentIndex = 0;
-            for (int i = 0; i < mLocales.size(); i++) {
-                if (mSelectedLanguageArray[i].equals(currentLanguage)) {
-                    mCurrentIndex = i;
-                    break;
-                }
-            }
-            // If we didn't find the index, use the first one
-        }
-        return true;
-    }
-
-    private void loadDefaults() {
-        if (LatinImeLogger.sDBG) {
-            Log.d(TAG, "load default locales:");
-        }
-        mDefaultInputLocale = mIme.getResources().getConfiguration().locale;
-        String country = mDefaultInputLocale.getCountry();
-        mDefaultInputLanguage = mDefaultInputLocale.getLanguage() +
-                (TextUtils.isEmpty(country) ? "" : "_" + country);
-    }
-
-    private void constructLocales() {
-        mLocales.clear();
-        for (final String lang : mSelectedLanguageArray) {
-            final Locale locale = LocaleUtils.constructLocaleFromString(lang);
-            mLocales.add(locale);
-        }
-    }
-
-    /**
-     * Returns the currently selected input language code, or the display language code if
-     * no specific locale was selected for input.
-     */
-    public String getInputLanguage() {
-        if (getLocaleCount() == 0) return mDefaultInputLanguage;
-
-        return mSelectedLanguageArray[mCurrentIndex];
-    }
-    
-    /**
-     * Returns the list of enabled language codes.
-     */
-    public String[] getEnabledLanguages(boolean allowImplicitlySelectedLanguages) {
-        if (mSelectedLanguageArray.length == 0 && allowImplicitlySelectedLanguages) {
-            return new String[] { mDefaultInputLanguage };
-        }
-        return mSelectedLanguageArray;
-    }
-
-    /**
-     * Returns the currently selected input locale, or the display locale if no specific
-     * locale was selected for input.
-     */
-    public Locale getInputLocale() {
-        if (getLocaleCount() == 0) return mDefaultInputLocale;
-
-        return mLocales.get(mCurrentIndex);
-    }
-
-    private int nextLocaleIndex() {
-        final int size = mLocales.size();
-        return (mCurrentIndex + 1) % size;
-    }
-
-    private int prevLocaleIndex() {
-        final int size = mLocales.size();
-        return (mCurrentIndex - 1 + size) % size;
-    }
-
-    /**
-     * Returns the next input locale in the list. Wraps around to the beginning of the
-     * list if we're at the end of the list.
-     */
-    public Locale getNextInputLocale() {
-        if (getLocaleCount() == 0) return mDefaultInputLocale;
-        return mLocales.get(nextLocaleIndex());
-    }
-
-    /**
-     * Sets the system locale (display UI) used for comparing with the input language.
-     * @param locale the locale of the system
-     */
-    private void setSystemLocale(Locale locale) {
-        mSystemLocale = locale;
-    }
-
-    /**
-     * Returns the system locale.
-     * @return the system locale
-     */
-    private Locale getSystemLocale() {
-        return mSystemLocale;
-    }
-
-    /**
-     * Returns the previous input locale in the list. Wraps around to the end of the
-     * list if we're at the beginning of the list.
-     */
-    public Locale getPrevInputLocale() {
-        if (getLocaleCount() == 0) return mDefaultInputLocale;
-        return mLocales.get(prevLocaleIndex());
-    }
-
-    public void reset() {
-        mCurrentIndex = 0;
-    }
-
-    public void next() {
-        mCurrentIndex = nextLocaleIndex();
-    }
-
-    public void prev() {
-        mCurrentIndex = prevLocaleIndex();
-    }
-
-    public void setLocale(String localeStr) {
-        final int N = mLocales.size();
-        for (int i = 0; i < N; ++i) {
-            if (mLocales.get(i).toString().equals(localeStr)) {
-                mCurrentIndex = i;
-            }
-        }
-    }
-
-    public void persist(SharedPreferences prefs) {
-        Editor editor = prefs.edit();
-        editor.putString(Settings.PREF_INPUT_LANGUAGE, getInputLanguage());
-        SharedPreferencesCompat.apply(editor);
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
deleted file mode 100644
index 3c79cc2..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
-
-/**
- * Represents information about a given text field, which can be passed
- * to the speech recognizer as context information.
- */
-public class FieldContext {
-    private static final boolean DBG = false;
-    
-    static final String LABEL = "label";
-    static final String HINT = "hint";
-    static final String PACKAGE_NAME = "packageName";
-    static final String FIELD_ID = "fieldId";
-    static final String FIELD_NAME = "fieldName";
-    static final String SINGLE_LINE = "singleLine";
-    static final String INPUT_TYPE = "inputType";
-    static final String IME_OPTIONS = "imeOptions";
-    static final String SELECTED_LANGUAGE = "selectedLanguage";
-    static final String ENABLED_LANGUAGES = "enabledLanguages";
-
-    Bundle mFieldInfo;
-
-    public FieldContext(InputConnection conn, EditorInfo info,
-            String selectedLanguage, String[] enabledLanguages) {
-        mFieldInfo = new Bundle();
-        addEditorInfoToBundle(info, mFieldInfo);
-        addInputConnectionToBundle(conn, mFieldInfo);
-        addLanguageInfoToBundle(selectedLanguage, enabledLanguages, mFieldInfo);
-        if (DBG) Log.i("FieldContext", "Bundle = " + mFieldInfo.toString());
-    }
-
-    private static String safeToString(Object o) {
-        if (o == null) {
-            return "";
-        }
-        return o.toString();
-    }
-
-    private static void addEditorInfoToBundle(EditorInfo info, Bundle bundle) {
-        if (info == null) {
-            return;
-        }
-
-        bundle.putString(LABEL, safeToString(info.label));
-        bundle.putString(HINT, safeToString(info.hintText));
-        bundle.putString(PACKAGE_NAME, safeToString(info.packageName));
-        bundle.putInt(FIELD_ID, info.fieldId);
-        bundle.putString(FIELD_NAME, safeToString(info.fieldName));
-        bundle.putInt(INPUT_TYPE, info.inputType);
-        bundle.putInt(IME_OPTIONS, info.imeOptions);
-    }
-
-    @SuppressWarnings("static-access")
-    private static void addInputConnectionToBundle(
-        InputConnection conn, Bundle bundle) {
-        if (conn == null) {
-            return;
-        }
-
-        ExtractedText et = conn.getExtractedText(new ExtractedTextRequest(), 0);
-        if (et == null) {
-            return;
-        }
-        bundle.putBoolean(SINGLE_LINE, (et.flags & et.FLAG_SINGLE_LINE) > 0);
-    }
-    
-    private static void addLanguageInfoToBundle(
-            String selectedLanguage, String[] enabledLanguages, Bundle bundle) {
-        bundle.putString(SELECTED_LANGUAGE, selectedLanguage);
-        bundle.putStringArray(ENABLED_LANGUAGES, enabledLanguages);
-    }
-
-    public Bundle getBundle() {
-        return mFieldInfo;
-    }
-
-    @Override
-    public String toString() {
-        return mFieldInfo.toString();
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/Hints.java b/java/src/com/android/inputmethod/deprecated/voice/Hints.java
deleted file mode 100644
index 17a19bf..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/Hints.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import com.android.inputmethod.compat.SharedPreferencesCompat;
-import com.android.inputmethod.latin.R;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.view.inputmethod.InputConnection;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Logic to determine when to display hints on usage to the user.
- */
-public class Hints {
-    public interface Display {
-        public void showHint(int viewResource);
-    }
-
-    private static final String PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN =
-            "voice_hint_num_unique_days_shown";
-    private static final String PREF_VOICE_HINT_LAST_TIME_SHOWN =
-            "voice_hint_last_time_shown";
-    private static final String PREF_VOICE_INPUT_LAST_TIME_USED =
-            "voice_input_last_time_used";
-    private static final String PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT =
-            "voice_punctuation_hint_view_count";
-    private static final int DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW = 7;
-    private static final int DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS = 7;
-
-    private final Context mContext;
-    private final SharedPreferences mPrefs;
-    private final Display mDisplay;
-    private boolean mVoiceResultContainedPunctuation;
-    private int mSwipeHintMaxDaysToShow;
-    private int mPunctuationHintMaxDisplays;
-
-    // Only show punctuation hint if voice result did not contain punctuation.
-    static final Map<CharSequence, String> SPEAKABLE_PUNCTUATION
-            = new HashMap<CharSequence, String>();
-    static {
-        SPEAKABLE_PUNCTUATION.put(",", "comma");
-        SPEAKABLE_PUNCTUATION.put(".", "period");
-        SPEAKABLE_PUNCTUATION.put("?", "question mark");
-    }
-
-    public Hints(Context context, SharedPreferences prefs, Display display) {
-        mContext = context;
-        mPrefs = prefs;
-        mDisplay = display;
-
-        ContentResolver cr = mContext.getContentResolver();
-        mSwipeHintMaxDaysToShow = SettingsUtil.getSettingsInt(
-                cr,
-                SettingsUtil.LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS,
-                DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW);
-        mPunctuationHintMaxDisplays = SettingsUtil.getSettingsInt(
-                cr,
-                SettingsUtil.LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS,
-                DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS);
-    }
-
-    public boolean showSwipeHintIfNecessary(boolean fieldRecommended) {
-        if (fieldRecommended && shouldShowSwipeHint()) {
-            showHint(R.layout.voice_swipe_hint);
-            return true;
-        }
-
-        return false;
-    }
-
-    public boolean showPunctuationHintIfNecessary(InputConnection ic) {
-        if (!mVoiceResultContainedPunctuation
-                && ic != null
-                && getAndIncrementPref(PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT)
-                        < mPunctuationHintMaxDisplays) {
-            CharSequence charBeforeCursor = ic.getTextBeforeCursor(1, 0);
-            if (SPEAKABLE_PUNCTUATION.containsKey(charBeforeCursor)) {
-                showHint(R.layout.voice_punctuation_hint);
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    public void registerVoiceResult(String text) {
-        // Update the current time as the last time voice input was used.
-        SharedPreferences.Editor editor = mPrefs.edit();
-        editor.putLong(PREF_VOICE_INPUT_LAST_TIME_USED, System.currentTimeMillis());
-        SharedPreferencesCompat.apply(editor);
-
-        mVoiceResultContainedPunctuation = false;
-        for (CharSequence s : SPEAKABLE_PUNCTUATION.keySet()) {
-            if (text.indexOf(s.toString()) >= 0) {
-                mVoiceResultContainedPunctuation = true;
-                break;
-            }
-        }
-    }
-
-    private boolean shouldShowSwipeHint() {
-        final SharedPreferences prefs = mPrefs;
-
-        int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
-
-        // If we've already shown the hint for enough days, we'll return false.
-        if (numUniqueDaysShown < mSwipeHintMaxDaysToShow) {
-
-            long lastTimeVoiceWasUsed = prefs.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0);
-
-            // If the user has used voice today, we'll return false. (We don't show the hint on
-            // any day that the user has already used voice.)
-            if (!isFromToday(lastTimeVoiceWasUsed)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Determines whether the provided time is from some time today (i.e., this day, month,
-     * and year).
-     */
-    private boolean isFromToday(long timeInMillis) {
-        if (timeInMillis == 0) return false;
-
-        Calendar today = Calendar.getInstance();
-        today.setTimeInMillis(System.currentTimeMillis());
-
-        Calendar timestamp = Calendar.getInstance();
-        timestamp.setTimeInMillis(timeInMillis);
-
-        return (today.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR) &&
-                today.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) &&
-                today.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH));
-    }
-
-    private void showHint(int hintViewResource) {
-        final SharedPreferences prefs = mPrefs;
-
-        int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
-        long lastTimeHintWasShown = prefs.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0);
-
-        // If this is the first time the hint is being shown today, increase the saved values
-        // to represent that. We don't need to increase the last time the hint was shown unless
-        // it is a different day from the current value.
-        if (!isFromToday(lastTimeHintWasShown)) {
-            SharedPreferences.Editor editor = prefs.edit();
-            editor.putInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, numUniqueDaysShown + 1);
-            editor.putLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, System.currentTimeMillis());
-            SharedPreferencesCompat.apply(editor);
-        }
-
-        if (mDisplay != null) {
-            mDisplay.showHint(hintViewResource);
-        }
-    }
-
-    private int getAndIncrementPref(String pref) {
-        final SharedPreferences prefs = mPrefs;
-        int value = prefs.getInt(pref, 0);
-        SharedPreferences.Editor editor = prefs.edit();
-        editor.putInt(pref, value + 1);
-        SharedPreferencesCompat.apply(editor);
-        return value;
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
deleted file mode 100644
index 71d15dc..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PathEffect;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-import java.util.Locale;
-
-/**
- * The user interface for the "Speak now" and "working" states.
- * Displays a recognition dialog (with waveform, voice meter, etc.),
- * plays beeps, shows errors, etc.
- */
-public class RecognitionView {
-    private static final String TAG = "RecognitionView";
-
-    private Handler mUiHandler;  // Reference to UI thread
-    private View mView;
-    private Context mContext;
-
-    private TextView mText;
-    private ImageView mImage;
-    private View mProgress;
-    private SoundIndicator mSoundIndicator;
-    private TextView mLanguage;
-    private Button mButton;
-
-    private Drawable mInitializing;
-    private Drawable mError;
-
-    private static final int INIT = 0;
-    private static final int LISTENING = 1;
-    private static final int WORKING = 2;
-    private static final int READY = 3;
-    
-    private int mState = INIT;
-
-    private final View mPopupLayout;
-
-    private final Drawable mListeningBorder;
-    private final Drawable mWorkingBorder;
-    private final Drawable mErrorBorder;
-
-    public RecognitionView(Context context, OnClickListener clickListener) {
-        mUiHandler = new Handler();
-
-        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-
-        mView = inflater.inflate(R.layout.recognition_status, null);
-
-        mPopupLayout= mView.findViewById(R.id.popup_layout);
-
-        // Pre-load volume level images
-        Resources r = context.getResources();
-
-        mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red);
-        mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue);
-        mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow);
-
-        mInitializing = r.getDrawable(R.drawable.mic_slash);
-        mError = r.getDrawable(R.drawable.caution);
-
-        mImage = (ImageView) mView.findViewById(R.id.image);
-        mProgress = mView.findViewById(R.id.progress);
-        mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator);
-
-        mButton = (Button) mView.findViewById(R.id.button);
-        mButton.setOnClickListener(clickListener);
-        mText = (TextView) mView.findViewById(R.id.text);
-        mLanguage = (TextView) mView.findViewById(R.id.language);
-
-        mContext = context;
-    }
-
-    public View getView() {
-        return mView;
-    }
-
-    public void restoreState() {
-        mUiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                // Restart the spinner
-                if (mState == WORKING) {
-                    ((ProgressBar) mProgress).setIndeterminate(false);
-                    ((ProgressBar) mProgress).setIndeterminate(true);
-                }
-            }
-        });
-    }
-
-    public void showInitializing() {
-        mUiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mState = INIT;
-                prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing,
-                        mContext.getText(R.string.cancel));
-            }
-          });
-    }
-
-    public void showListening() {
-        Log.d(TAG, "#showListening");
-        mUiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mState = LISTENING;
-                prepareDialog(mContext.getText(R.string.voice_listening), null,
-                        mContext.getText(R.string.cancel));
-            }
-          });
-    }
-
-    public void updateVoiceMeter(float rmsdB) {
-        mSoundIndicator.setRmsdB(rmsdB);
-    }
-
-    public void showError(final String message) {
-        mUiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mState = READY;
-                prepareDialog(message, mError, mContext.getText(R.string.ok));
-            }
-        });
-    }
-
-    public void showWorking(
-        final ByteArrayOutputStream waveBuffer,
-        final int speechStartPosition,
-        final int speechEndPosition) {
-        mUiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mState = WORKING;
-                prepareDialog(mContext.getText(R.string.voice_working), null, mContext
-                        .getText(R.string.cancel));
-                final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order(
-                        ByteOrder.nativeOrder()).asShortBuffer();
-                buf.position(0);
-                waveBuffer.reset();
-                showWave(buf, speechStartPosition / 2, speechEndPosition / 2);
-            }
-        });
-    }
-    
-    private void prepareDialog(CharSequence text, Drawable image,
-            CharSequence btnTxt) {
-
-        /*
-         * The mic of INIT and of LISTENING has to be displayed in the same position. To accomplish
-         * that, some text visibility are not set as GONE but as INVISIBLE.
-         */
-        switch (mState) {
-            case INIT:
-                mText.setVisibility(View.INVISIBLE);
-
-                mProgress.setVisibility(View.GONE);
-
-                mImage.setVisibility(View.VISIBLE);
-                mImage.setImageResource(R.drawable.mic_slash);
-
-                mSoundIndicator.setVisibility(View.GONE);
-                mSoundIndicator.stop();
-
-                mLanguage.setVisibility(View.INVISIBLE);
-
-                mPopupLayout.setBackgroundDrawable(mListeningBorder);
-                break;
-            case LISTENING:
-                mText.setVisibility(View.VISIBLE);
-                mText.setText(text);
-
-                mProgress.setVisibility(View.GONE);
-
-                mImage.setVisibility(View.GONE);
-
-                mSoundIndicator.setVisibility(View.VISIBLE);
-                mSoundIndicator.start();
-
-                Locale locale = SubtypeSwitcher.getInstance().getInputLocale();
-
-                mLanguage.setVisibility(View.VISIBLE);
-                mLanguage.setText(Utils.getFullDisplayName(locale, true));
-
-                mPopupLayout.setBackgroundDrawable(mListeningBorder);
-                break;
-            case WORKING:
-
-                mText.setVisibility(View.VISIBLE);
-                mText.setText(text);
-
-                mProgress.setVisibility(View.VISIBLE);
-
-                mImage.setVisibility(View.VISIBLE);
-
-                mSoundIndicator.setVisibility(View.GONE);
-                mSoundIndicator.stop();
-
-                mLanguage.setVisibility(View.GONE);
-
-                mPopupLayout.setBackgroundDrawable(mWorkingBorder);
-                break;
-            case READY:
-                mText.setVisibility(View.VISIBLE);
-                mText.setText(text);
-
-                mProgress.setVisibility(View.GONE);
-
-                mImage.setVisibility(View.VISIBLE);
-                mImage.setImageResource(R.drawable.caution);
-
-                mSoundIndicator.setVisibility(View.GONE);
-                mSoundIndicator.stop();
-
-                mLanguage.setVisibility(View.GONE);
-
-                mPopupLayout.setBackgroundDrawable(mErrorBorder);
-                break;
-             default:
-                 Log.w(TAG, "Unknown state " + mState);
-        }
-        mPopupLayout.requestLayout();
-        mButton.setText(btnTxt);
-    }
-
-    /**
-     * @return an average abs of the specified buffer.
-     */
-    private static int getAverageAbs(ShortBuffer buffer, int start, int i, int npw) {
-        int from = start + i * npw;
-        int end = from + npw;
-        int total = 0;
-        for (int x = from; x < end; x++) {
-            total += Math.abs(buffer.get(x));
-        }
-        return total / npw;
-    }
-
-
-    /**
-     * Shows waveform of input audio.
-     *
-     * Copied from version in VoiceSearch's RecognitionActivity.
-     *
-     * TODO: adjust stroke width based on the size of data.
-     * TODO: use dip rather than pixels.
-     */
-    private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) {
-        final int w = ((View) mImage.getParent()).getWidth();
-        final int h = ((View) mImage.getParent()).getHeight();
-        if (w <= 0 || h <= 0) {
-            // view is not visible this time. Skip drawing.
-            return;
-        }
-        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
-        final Canvas c = new Canvas(b);
-        final Paint paint = new Paint();
-        paint.setColor(0xFFFFFFFF); // 0xAARRGGBB
-        paint.setAntiAlias(true);
-        paint.setStyle(Paint.Style.STROKE);
-        paint.setAlpha(80);
-
-        final PathEffect effect = new CornerPathEffect(3);
-        paint.setPathEffect(effect);
-
-        final int numSamples = waveBuffer.remaining();
-        int endIndex;
-        if (endPosition == 0) {
-            endIndex = numSamples;
-        } else {
-            endIndex = Math.min(endPosition, numSamples);
-        }
-
-        int startIndex = startPosition - 2000; // include 250ms before speech
-        if (startIndex < 0) {
-            startIndex = 0;
-        }
-        final int numSamplePerWave = 200;  // 8KHz 25ms = 200 samples
-        final float scale = 10.0f / 65536.0f;
-
-        final int count = (endIndex - startIndex) / numSamplePerWave;
-        final float deltaX = 1.0f * w / count;
-        int yMax = h / 2;
-        Path path = new Path();
-        c.translate(0, yMax);
-        float x = 0;
-        path.moveTo(x, 0);
-        for (int i = 0; i < count; i++) {
-            final int avabs = getAverageAbs(waveBuffer, startIndex, i , numSamplePerWave);
-            int sign = ( (i & 01) == 0) ? -1 : 1;
-            final float y = Math.min(yMax, avabs * h * scale) * sign;
-            path.lineTo(x, y);
-            x += deltaX;
-            path.lineTo(x, y);
-        }
-        if (deltaX > 4) {
-            paint.setStrokeWidth(2);
-        } else {
-            paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05)));
-        }
-        c.drawPath(path, paint);
-        mImage.setImageBitmap(b);
-    }
-
-    public void finish() {
-        mUiHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mSoundIndicator.stop();
-            }
-        });
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
deleted file mode 100644
index 855a09a..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import android.content.ContentResolver;
-import android.provider.Settings;
-
-/**
- * Utility for retrieving settings from Settings.Secure.
- */
-public class SettingsUtil {
-    /**
-     * A whitespace-separated list of supported locales for voice input from the keyboard.
-     */
-    public static final String LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES =
-            "latin_ime_voice_input_supported_locales";
-
-    /**
-     * A whitespace-separated list of recommended app packages for voice input from the
-     * keyboard.
-     */
-    public static final String LATIN_IME_VOICE_INPUT_RECOMMENDED_PACKAGES =
-            "latin_ime_voice_input_recommended_packages";
-
-    /**
-     * The maximum number of unique days to show the swipe hint for voice input.
-     */
-    public static final String LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS =
-            "latin_ime_voice_input_swipe_hint_max_days";
-
-    /**
-     * The maximum number of times to show the punctuation hint for voice input.
-     */
-    public static final String LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS =
-            "latin_ime_voice_input_punctuation_hint_max_displays";
-
-    /**
-     * Endpointer parameters for voice input from the keyboard.
-     */
-    public static final String LATIN_IME_SPEECH_MINIMUM_LENGTH_MILLIS =
-            "latin_ime_speech_minimum_length_millis";
-    public static final String LATIN_IME_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS =
-            "latin_ime_speech_input_complete_silence_length_millis";
-    public static final String LATIN_IME_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS =
-            "latin_ime_speech_input_possibly_complete_silence_length_millis";
-
-    /**
-     * Min and max volume levels that can be displayed on the "speak now" screen.
-     */
-    public static final String LATIN_IME_MIN_MICROPHONE_LEVEL =
-            "latin_ime_min_microphone_level";
-    public static final String LATIN_IME_MAX_MICROPHONE_LEVEL =
-            "latin_ime_max_microphone_level";
-
-    /**
-     * The number of sentence-level alternates to request of the server.
-     */
-    public static final String LATIN_IME_MAX_VOICE_RESULTS = "latin_ime_max_voice_results";
-
-    /**
-     * Get a string-valued setting.
-     *
-     * @param cr The content resolver to use
-     * @param key The setting to look up
-     * @param defaultValue The default value to use if none can be found
-     * @return The value of the setting, or defaultValue if it couldn't be found
-     */
-    public static String getSettingsString(ContentResolver cr, String key, String defaultValue) {
-        String result = Settings.Secure.getString(cr, key);
-        return (result == null) ? defaultValue : result;
-    }
-
-    /**
-     * Get an int-valued setting.
-     *
-     * @param cr The content resolver to use
-     * @param key The setting to look up
-     * @param defaultValue The default value to use if the setting couldn't be found or parsed
-     * @return The value of the setting, or defaultValue if it couldn't be found or parsed
-     */
-    public static int getSettingsInt(ContentResolver cr, String key, int defaultValue) {
-        return Settings.Secure.getInt(cr, key, defaultValue);
-    }
-
-    /**
-     * Get a float-valued setting.
-     *
-     * @param cr The content resolver to use
-     * @param key The setting to look up
-     * @param defaultValue The default value to use if the setting couldn't be found or parsed
-     * @return The value of the setting, or defaultValue if it couldn't be found or parsed
-     */
-    public static float getSettingsFloat(ContentResolver cr, String key, float defaultValue) {
-        return Settings.Secure.getFloat(cr, key, defaultValue);
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
deleted file mode 100644
index 25b3140..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.inputmethod.latin.R;
-
-/**
- * A widget which shows the volume of audio using a microphone icon
- */
-public class SoundIndicator extends ImageView {
-    @SuppressWarnings("unused")
-    private static final String TAG = "SoundIndicator";
-
-    private static final float UP_SMOOTHING_FACTOR = 0.9f;
-    private static final float DOWN_SMOOTHING_FACTOR = 0.4f;
-
-    private static final float AUDIO_METER_MIN_DB = 7.0f;
-    private static final float AUDIO_METER_DB_RANGE = 20.0f;
-
-    private static final long FRAME_DELAY = 50;
-
-    private Bitmap mDrawingBuffer;
-    private Canvas mBufferCanvas;
-    private Bitmap mEdgeBitmap;
-    private float mLevel = 0.0f;
-    private Drawable mFrontDrawable;
-    private Paint mClearPaint;
-    private Paint mMultPaint;
-    private int mEdgeBitmapOffset;
-
-    private Handler mHandler;
-
-    private Runnable mDrawFrame = new Runnable() {
-        public void run() {
-            invalidate();
-            mHandler.postDelayed(mDrawFrame, FRAME_DELAY);
-        }
-    };
-
-    public SoundIndicator(Context context) {
-        this(context, null);
-    }
-
-    public SoundIndicator(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mFrontDrawable = getDrawable();
-        BitmapDrawable edgeDrawable =
-                (BitmapDrawable) context.getResources().getDrawable(R.drawable.vs_popup_mic_edge);
-        mEdgeBitmap = edgeDrawable.getBitmap();
-        mEdgeBitmapOffset = mEdgeBitmap.getHeight() / 2;
-
-        mDrawingBuffer =
-                Bitmap.createBitmap(mFrontDrawable.getIntrinsicWidth(),
-                        mFrontDrawable.getIntrinsicHeight(), Config.ARGB_8888);
-
-        mBufferCanvas = new Canvas(mDrawingBuffer);
-
-        // Initialize Paints.
-        mClearPaint = new Paint();
-        mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
-        mMultPaint = new Paint();
-        mMultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
-
-        mHandler = new Handler();
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        //super.onDraw(canvas);
-
-        float w = getWidth();
-        float h = getHeight();
-
-        // Clear the buffer canvas
-        mBufferCanvas.drawRect(0, 0, w, h, mClearPaint);
-
-        // Set its clip so we don't draw the front image all the way to the top
-        Rect clip = new Rect(0,
-                (int) ((1.0 - mLevel) * (h + mEdgeBitmapOffset)) - mEdgeBitmapOffset,
-                (int) w,
-                (int) h);
-
-        mBufferCanvas.save();
-        mBufferCanvas.clipRect(clip);
-
-        // Draw the front image
-        mFrontDrawable.setBounds(new Rect(0, 0, (int) w, (int) h));
-        mFrontDrawable.draw(mBufferCanvas);
-
-        mBufferCanvas.restore();
-
-        // Draw the edge image on top of the buffer image with a multiply mode
-        mBufferCanvas.drawBitmap(mEdgeBitmap, 0, clip.top, mMultPaint);
-
-        // Draw the buffer image (on top of the background image)
-        canvas.drawBitmap(mDrawingBuffer, 0, 0, null);
-    }
-
-    /**
-     * Sets the sound level
-     *
-     * @param rmsdB The level of the sound, in dB.
-     */
-    public void setRmsdB(float rmsdB) {
-        float level = ((rmsdB - AUDIO_METER_MIN_DB) / AUDIO_METER_DB_RANGE);
-
-        level = Math.min(Math.max(0.0f, level), 1.0f);
-
-        // We smooth towards the new level
-        if (level > mLevel) {
-            mLevel = (level - mLevel) * UP_SMOOTHING_FACTOR + mLevel;
-        } else {
-            mLevel = (level - mLevel) * DOWN_SMOOTHING_FACTOR + mLevel;
-        }
-        invalidate();
-    }
-
-    public void start() {
-        mHandler.post(mDrawFrame);
-    }
-
-    public void stop() {
-        mHandler.removeCallbacks(mDrawFrame);
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
deleted file mode 100644
index 8969a21..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import com.android.inputmethod.latin.EditingUtils;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Parcelable;
-import android.speech.RecognitionListener;
-import android.speech.RecognizerIntent;
-import android.speech.SpeechRecognizer;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.inputmethod.InputConnection;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * Speech recognition input, including both user interface and a background
- * process to stream audio to the network recognizer. This class supplies a
- * View (getView()), which it updates as recognition occurs. The user of this
- * class is responsible for making the view visible to the user, as well as
- * handling various events returned through UiListener.
- */
-public class VoiceInput implements OnClickListener {
-    private static final String TAG = "VoiceInput";
-    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 boolean DBG = LatinImeLogger.sDBG;
-
-    private static final String DEFAULT_RECOMMENDED_PACKAGES =
-            "com.android.mms " +
-            "com.google.android.gm " +
-            "com.google.android.talk " +
-            "com.google.android.apps.googlevoice " +
-            "com.android.email " +
-            "com.android.browser ";
-
-    // 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 = true;
-
-    // Dummy word suggestion which means "delete current word"
-    public static final String DELETE_SYMBOL = " \u00D7 ";  // times symbol
-
-    private Whitelist mRecommendedList;
-    private Whitelist mBlacklist;
-
-    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.
-    @SuppressWarnings("unused")
-    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 =
-            "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
-    private static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS =
-            "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
-    private static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS =
-            "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
-
-    // The usual endpointer default value for input complete silence length is 0.5 seconds,
-    // but that's used for things like voice search. For dictation-like voice input like this,
-    // we go with a more liberal value of 1 second. This value will only be used if a value
-    // is not provided from Gservices.
-    private static final String INPUT_COMPLETE_SILENCE_LENGTH_DEFAULT_VALUE_MILLIS = "1000";
-
-    // Used to record part of that state for logging purposes.
-    public static final int DEFAULT = 0;
-    public static final int LISTENING = 1;
-    public static final int WORKING = 2;
-    public static final int ERROR = 3;
-
-    private int mAfterVoiceInputDeleteCount = 0;
-    private int mAfterVoiceInputInsertCount = 0;
-    private int mAfterVoiceInputInsertPunctuationCount = 0;
-    private int mAfterVoiceInputCursorPos = 0;
-    private int mAfterVoiceInputSelectionSpan = 0;
-
-    private int mState = DEFAULT;
-
-    private final static int MSG_RESET = 1;
-
-    private final UIHandler mHandler = new UIHandler(this);
-
-    private static class UIHandler extends StaticInnerHandlerWrapper<VoiceInput> {
-        public UIHandler(VoiceInput outerInstance) {
-            super(outerInstance);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_RESET) {
-                final VoiceInput voiceInput = getOuterInstance();
-                voiceInput.mState = DEFAULT;
-                voiceInput.mRecognitionView.finish();
-                voiceInput.mUiListener.onCancelVoice();
-            }
-        }
-    };
-
-    /**
-     * Events relating to the recognition UI. You must implement these.
-     */
-    public interface UiListener {
-
-        /**
-         * @param recognitionResults a set of transcripts for what the user
-         *   spoke, sorted by likelihood.
-         */
-        public void onVoiceResults(
-            List<String> recognitionResults,
-            Map<String, List<CharSequence>> alternatives);
-
-        /**
-         * Called when the user cancels speech recognition.
-         */
-        public void onCancelVoice();
-    }
-
-    private SpeechRecognizer mSpeechRecognizer;
-    private RecognitionListener mRecognitionListener;
-    private RecognitionView mRecognitionView;
-    private UiListener mUiListener;
-    private Context mContext;
-
-    /**
-     * @param context the service or activity in which we're running.
-     * @param uiHandler object to receive events from VoiceInput.
-     */
-    public VoiceInput(Context context, UiListener uiHandler) {
-        mLogger = VoiceInputLogger.getLogger(context);
-        mRecognitionListener = new ImeRecognitionListener();
-        mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
-        mSpeechRecognizer.setRecognitionListener(mRecognitionListener);
-        mUiListener = uiHandler;
-        mContext = context;
-        newView();
-
-        String recommendedPackages = SettingsUtil.getSettingsString(
-                context.getContentResolver(),
-                SettingsUtil.LATIN_IME_VOICE_INPUT_RECOMMENDED_PACKAGES,
-                DEFAULT_RECOMMENDED_PACKAGES);
-
-        mRecommendedList = new Whitelist();
-        for (String recommendedPackage : recommendedPackages.split("\\s+")) {
-            mRecommendedList.addApp(recommendedPackage);
-        }
-
-        mBlacklist = new Whitelist();
-        mBlacklist.addApp("com.google.android.setupwizard");
-    }
-
-    public void setCursorPos(int pos) {
-        mAfterVoiceInputCursorPos = pos;
-    }
-
-    public int getCursorPos() {
-        return mAfterVoiceInputCursorPos;
-    }
-
-    public void setSelectionSpan(int span) {
-        mAfterVoiceInputSelectionSpan = span;
-    }
-
-    public int getSelectionSpan() {
-        return mAfterVoiceInputSelectionSpan;
-    }
-
-    public void incrementTextModificationDeleteCount(int count){
-        mAfterVoiceInputDeleteCount += count;
-        // Send up intents for other text modification types
-        if (mAfterVoiceInputInsertCount > 0) {
-            logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount);
-            mAfterVoiceInputInsertCount = 0;
-        }
-        if (mAfterVoiceInputInsertPunctuationCount > 0) {
-            logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount);
-            mAfterVoiceInputInsertPunctuationCount = 0;
-        }
-
-    }
-
-    public void incrementTextModificationInsertCount(int count){
-        mAfterVoiceInputInsertCount += count;
-        if (mAfterVoiceInputSelectionSpan > 0) {
-            // If text was highlighted before inserting the char, count this as
-            // a delete.
-            mAfterVoiceInputDeleteCount += mAfterVoiceInputSelectionSpan;
-        }
-        // Send up intents for other text modification types
-        if (mAfterVoiceInputDeleteCount > 0) {
-            logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount);
-            mAfterVoiceInputDeleteCount = 0;
-        }
-        if (mAfterVoiceInputInsertPunctuationCount > 0) {
-            logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount);
-            mAfterVoiceInputInsertPunctuationCount = 0;
-        }
-    }
-
-    public void incrementTextModificationInsertPunctuationCount(int count){
-        mAfterVoiceInputInsertPunctuationCount += count;
-        if (mAfterVoiceInputSelectionSpan > 0) {
-            // If text was highlighted before inserting the char, count this as
-            // a delete.
-            mAfterVoiceInputDeleteCount += mAfterVoiceInputSelectionSpan;
-        }
-        // Send up intents for aggregated non-punctuation insertions
-        if (mAfterVoiceInputDeleteCount > 0) {
-            logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount);
-            mAfterVoiceInputDeleteCount = 0;
-        }
-        if (mAfterVoiceInputInsertCount > 0) {
-            logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount);
-            mAfterVoiceInputInsertCount = 0;
-        }
-    }
-
-    public void flushAllTextModificationCounters() {
-        if (mAfterVoiceInputInsertCount > 0) {
-            logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount);
-            mAfterVoiceInputInsertCount = 0;
-        }
-        if (mAfterVoiceInputDeleteCount > 0) {
-            logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount);
-            mAfterVoiceInputDeleteCount = 0;
-        }
-        if (mAfterVoiceInputInsertPunctuationCount > 0) {
-            logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount);
-            mAfterVoiceInputInsertPunctuationCount = 0;
-        }
-    }
-
-    /**
-     * The configuration of the IME changed and may have caused the views to be layed out
-     * again. Restore the state of the recognition view.
-     */
-    public void onConfigurationChanged(Configuration configuration) {
-        mRecognitionView.restoreState();
-        mRecognitionView.getView().dispatchConfigurationChanged(configuration);
-    }
-
-    /**
-     * @return true if field is blacklisted for voice
-     */
-    public boolean isBlacklistedField(FieldContext context) {
-        return mBlacklist.matches(context);
-    }
-
-    /**
-     * Used to decide whether to show voice input hints for this field, etc.
-     *
-     * @return true if field is recommended for voice
-     */
-    public boolean isRecommendedField(FieldContext context) {
-        return mRecommendedList.matches(context);
-    }
-
-    /**
-     * Start listening for speech from the user. This will grab the microphone
-     * and start updating the view provided by getView(). It is the caller's
-     * responsibility to ensure that the view is visible to the user at this stage.
-     *
-     * @param context the same FieldContext supplied to voiceIsEnabled()
-     * @param swipe whether this voice input was started by swipe, for logging purposes
-     */
-    public void startListening(FieldContext context, boolean swipe) {
-        if (DBG) {
-            Log.d(TAG, "startListening: " + context);
-        }
-
-        if (mState != DEFAULT) {
-            Log.w(TAG, "startListening in the wrong status " + mState);
-        }
-
-        // If everything works ok, the voice input should be already in the correct state. As this
-        // class can be called by third-party, we call reset just to be on the safe side.
-        reset();
-
-        Locale locale = Locale.getDefault();
-        String localeString = locale.getLanguage() + "-" + locale.getCountry();
-
-        mLogger.start(localeString, swipe);
-
-        mState = LISTENING;
-
-        mRecognitionView.showInitializing();
-        startListeningAfterInitialization(context);
-    }
-
-    /**
-     * Called only when the recognition manager's initialization completed
-     *
-     * @param context context with which {@link #startListening(FieldContext, boolean)} was executed
-     */
-    private void startListeningAfterInitialization(FieldContext context) {
-        Intent intent = makeIntent();
-        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();
-        putEndpointerExtra(
-                cr,
-                intent,
-                SettingsUtil.LATIN_IME_SPEECH_MINIMUM_LENGTH_MILLIS,
-                EXTRA_SPEECH_MINIMUM_LENGTH_MILLIS,
-                null  /* rely on endpointer default */);
-        putEndpointerExtra(
-                cr,
-                intent,
-                SettingsUtil.LATIN_IME_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS,
-                EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS,
-                INPUT_COMPLETE_SILENCE_LENGTH_DEFAULT_VALUE_MILLIS
-                /* our default value is different from the endpointer's */);
-        putEndpointerExtra(
-                cr,
-                intent,
-                SettingsUtil.
-                        LATIN_IME_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS,
-                EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS,
-                null  /* rely on endpointer default */);
-
-        mSpeechRecognizer.startListening(intent);
-    }
-
-    /**
-     * Gets the value of the provided Gservices key, attempts to parse it into a long,
-     * and if successful, puts the long value as an extra in the provided intent.
-     */
-    private void putEndpointerExtra(ContentResolver cr, Intent i,
-            String gservicesKey, String intentExtraKey, String defaultValue) {
-        long l = -1;
-        String s = SettingsUtil.getSettingsString(cr, gservicesKey, defaultValue);
-        if (s != null) {
-            try {
-                l = Long.valueOf(s);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "could not parse value for " + gservicesKey + ": " + s);
-            }
-        }
-
-        if (l != -1) i.putExtra(intentExtraKey, l);
-    }
-
-    public void destroy() {
-        mSpeechRecognizer.destroy();
-    }
-
-    /**
-     * Creates a new instance of the view that is returned by {@link #getView()}
-     * Clients should use this when a previously returned view is stuck in a
-     * layout that is being thrown away and a new one is need to show to the
-     * user.
-     */
-    public void newView() {
-        mRecognitionView = new RecognitionView(mContext, this);
-    }
-
-    /**
-     * @return a view that shows the recognition flow--e.g., "Speak now" and
-     * "working" dialogs.
-     */
-    public View getView() {
-        return mRecognitionView.getView();
-    }
-
-    /**
-     * Handle the cancel button.
-     */
-    @Override
-    public void onClick(View view) {
-        switch(view.getId()) {
-            case R.id.button:
-                cancel();
-                break;
-        }
-    }
-
-    public void logTextModifiedByTypingInsertion(int length) {
-        mLogger.textModifiedByTypingInsertion(length);
-    }
-
-    public void logTextModifiedByTypingInsertionPunctuation(int length) {
-        mLogger.textModifiedByTypingInsertionPunctuation(length);
-    }
-
-    public void logTextModifiedByTypingDeletion(int length) {
-        mLogger.textModifiedByTypingDeletion(length);
-    }
-
-    public void logTextModifiedByChooseSuggestion(String suggestion, int index,
-                                                  String wordSeparators, InputConnection ic) {
-        String wordToBeReplaced = EditingUtils.getWordAtCursor(ic, wordSeparators);
-        // If we enable phrase-based alternatives, only send up the first word
-        // in suggestion and wordToBeReplaced.
-        mLogger.textModifiedByChooseSuggestion(suggestion.length(), wordToBeReplaced.length(),
-                                               index, wordToBeReplaced, suggestion);
-    }
-
-    public void logKeyboardWarningDialogShown() {
-        mLogger.keyboardWarningDialogShown();
-    }
-
-    public void logKeyboardWarningDialogDismissed() {
-        mLogger.keyboardWarningDialogDismissed();
-    }
-
-    public void logKeyboardWarningDialogOk() {
-        mLogger.keyboardWarningDialogOk();
-    }
-
-    public void logKeyboardWarningDialogCancel() {
-        mLogger.keyboardWarningDialogCancel();
-    }
-
-    public void logSwipeHintDisplayed() {
-        mLogger.swipeHintDisplayed();
-    }
-
-    public void logPunctuationHintDisplayed() {
-        mLogger.punctuationHintDisplayed();
-    }
-
-    public void logVoiceInputDelivered(int length) {
-        mLogger.voiceInputDelivered(length);
-    }
-
-    public void logInputEnded() {
-        mLogger.inputEnded();
-    }
-
-    public void flushLogs() {
-        mLogger.flush();
-    }
-
-    private static Intent makeIntent() {
-        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-
-        // On Cupcake, use VoiceIMEHelper since VoiceSearch doesn't support.
-        // On Donut, always use VoiceSearch, since VoiceIMEHelper and
-        // VoiceSearch may conflict.
-        if (Build.VERSION.RELEASE.equals("1.5")) {
-            intent = intent.setClassName(
-              "com.google.android.voiceservice",
-              "com.google.android.voiceservice.IMERecognitionService");
-        } else {
-            intent = intent.setClassName(
-              "com.google.android.voicesearch",
-              "com.google.android.voicesearch.RecognitionService");
-        }
-
-        return intent;
-    }
-
-    /**
-     * Reset the current voice recognition.
-     */
-    public void reset() {
-        if (mState != DEFAULT) {
-            mState = DEFAULT;
-
-            // Remove all pending tasks (e.g., timers to cancel voice input)
-            mHandler.removeMessages(MSG_RESET);
-
-            mSpeechRecognizer.cancel();
-            mRecognitionView.finish();
-        }
-    }
-
-    /**
-     * Cancel in-progress speech recognition.
-     */
-    public void cancel() {
-        switch (mState) {
-        case LISTENING:
-            mLogger.cancelDuringListening();
-            break;
-        case WORKING:
-            mLogger.cancelDuringWorking();
-            break;
-        case ERROR:
-            mLogger.cancelDuringError();
-            break;
-        }
-
-        reset();
-        mUiListener.onCancelVoice();
-    }
-
-    private int getErrorStringId(int errorType, boolean endpointed) {
-        switch (errorType) {
-            // We use CLIENT_ERROR to signify that voice search is not available on the device.
-            case SpeechRecognizer.ERROR_CLIENT:
-                return R.string.voice_not_installed;
-            case SpeechRecognizer.ERROR_NETWORK:
-                return R.string.voice_network_error;
-            case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
-                return endpointed ?
-                        R.string.voice_network_error : R.string.voice_too_much_speech;
-            case SpeechRecognizer.ERROR_AUDIO:
-                return R.string.voice_audio_error;
-            case SpeechRecognizer.ERROR_SERVER:
-                return R.string.voice_server_error;
-            case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
-                return R.string.voice_speech_timeout;
-            case SpeechRecognizer.ERROR_NO_MATCH:
-                return R.string.voice_no_match;
-            default: return R.string.voice_error;
-        }
-    }
-
-    private void onError(int errorType, boolean endpointed) {
-        Log.i(TAG, "error " + errorType);
-        mLogger.error(errorType);
-        onError(mContext.getString(getErrorStringId(errorType, endpointed)));
-    }
-
-    private void onError(String error) {
-        mState = ERROR;
-        mRecognitionView.showError(error);
-        // Wait a couple seconds and then automatically dismiss message.
-        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_RESET), 2000);
-    }
-
-    private class ImeRecognitionListener implements RecognitionListener {
-        // Waveform data
-        final ByteArrayOutputStream mWaveBuffer = new ByteArrayOutputStream();
-        int mSpeechStart;
-        private boolean mEndpointed = false;
-
-        @Override
-        public void onReadyForSpeech(Bundle noiseParams) {
-            mRecognitionView.showListening();
-        }
-
-        @Override
-        public void onBeginningOfSpeech() {
-            mEndpointed = false;
-            mSpeechStart = mWaveBuffer.size();
-        }
-
-        @Override
-        public void onRmsChanged(float rmsdB) {
-            mRecognitionView.updateVoiceMeter(rmsdB);
-        }
-
-        @Override
-        public void onBufferReceived(byte[] buf) {
-            try {
-                mWaveBuffer.write(buf);
-            } catch (IOException e) {
-                // ignore.
-            }
-        }
-
-        @Override
-        public void onEndOfSpeech() {
-            mEndpointed = true;
-            mState = WORKING;
-            mRecognitionView.showWorking(mWaveBuffer, mSpeechStart, mWaveBuffer.size());
-        }
-
-        @Override
-        public void onError(int errorType) {
-            mState = ERROR;
-            VoiceInput.this.onError(errorType, mEndpointed);
-        }
-
-        @Override
-        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 (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);
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (results.size() > 5) {
-                results = results.subList(0, 5);
-            }
-            mUiListener.onVoiceResults(results, alternatives);
-            mRecognitionView.finish();
-        }
-
-        @Override
-        public void onPartialResults(final Bundle partialResults) {
-            // currently - do nothing
-        }
-
-        @Override
-        public void onEvent(int eventType, Bundle params) {
-            // do nothing - reserved for events that might be added in the future
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
deleted file mode 100644
index 22e8207..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import com.android.common.speech.LoggingEvents;
-import com.android.inputmethod.deprecated.compat.VoiceInputLoggerCompatUtils;
-
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Provides the logging facility for voice input events. This fires broadcasts back to
- * the voice search app which then logs on our behalf.
- *
- * Note that debug console logging does not occur in this class. If you want to
- * see console output of these logging events, there is a boolean switch to turn
- * on on the VoiceSearch side.
- */
-public class VoiceInputLogger {
-    @SuppressWarnings("unused")
-    private static final String TAG = VoiceInputLogger.class.getSimpleName();
-
-    private static VoiceInputLogger sVoiceInputLogger;
-
-    private final Context mContext;
-
-    // The base intent used to form all broadcast intents to the logger
-    // in VoiceSearch.
-    private final Intent mBaseIntent;
-
-    // This flag is used to indicate when there are voice events that
-    // need to be flushed.
-    private boolean mHasLoggingInfo = false;
-
-    /**
-     * Returns the singleton of the logger.
-     *
-     * @param contextHint a hint context used when creating the logger instance.
-     * Ignored if the singleton instance already exists.
-     */
-    public static synchronized VoiceInputLogger getLogger(Context contextHint) {
-        if (sVoiceInputLogger == null) {
-            sVoiceInputLogger = new VoiceInputLogger(contextHint);
-        }
-        return sVoiceInputLogger;
-    }
-
-    public VoiceInputLogger(Context context) {
-        mContext = context;
-        
-        mBaseIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
-        mBaseIntent.putExtra(LoggingEvents.EXTRA_APP_NAME, LoggingEvents.VoiceIme.APP_NAME);
-    }
-    
-    private Intent newLoggingBroadcast(int event) {
-        Intent i = new Intent(mBaseIntent);
-        i.putExtra(LoggingEvents.EXTRA_EVENT, event);
-        return i;
-    }
-
-    public void flush() {
-        if (hasLoggingInfo()) {
-            Intent i = new Intent(mBaseIntent);
-            i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
-            mContext.sendBroadcast(i);
-            setHasLoggingInfo(false);
-        }
-    }
-    
-    public void keyboardWarningDialogShown() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_SHOWN));
-    }
-    
-    public void keyboardWarningDialogDismissed() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_DISMISSED));
-    }
-
-    public void keyboardWarningDialogOk() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_OK));
-    }
-
-    public void keyboardWarningDialogCancel() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_CANCEL));
-    }
-
-    public void settingsWarningDialogShown() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_SHOWN));
-    }
-    
-    public void settingsWarningDialogDismissed() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_DISMISSED));
-    }
-
-    public void settingsWarningDialogOk() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_OK));
-    }
-
-    public void settingsWarningDialogCancel() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_CANCEL));
-    }
-    
-    public void swipeHintDisplayed() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.SWIPE_HINT_DISPLAYED));
-    }
-    
-    public void cancelDuringListening() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_LISTENING));
-    }
-
-    public void cancelDuringWorking() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_WORKING));
-    }
-
-    public void cancelDuringError() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_ERROR));
-    }
-    
-    public void punctuationHintDisplayed() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.PUNCTUATION_HINT_DISPLAYED));
-    }
-    
-    public void error(int code) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.ERROR);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_ERROR_CODE, code);
-        mContext.sendBroadcast(i);
-    }
-
-    public void start(String locale, boolean swipe) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.START);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_LOCALE, locale);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_SWIPE, swipe);
-        i.putExtra(LoggingEvents.EXTRA_TIMESTAMP, System.currentTimeMillis());
-        mContext.sendBroadcast(i);
-    }
-    
-    public void voiceInputDelivered(int length) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.VOICE_INPUT_DELIVERED);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
-        mContext.sendBroadcast(i);
-    }
-
-    public void textModifiedByTypingInsertion(int length) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
-                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION);
-        mContext.sendBroadcast(i);
-    }
-
-    public void textModifiedByTypingInsertionPunctuation(int length) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
-                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION_PUNCTUATION);
-        mContext.sendBroadcast(i);
-    }
-
-    public void textModifiedByTypingDeletion(int length) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
-                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_DELETION);
-
-        mContext.sendBroadcast(i);
-    }
-
-
-    public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength,
-                                               int index, String before, String after) {
-        setHasLoggingInfo(true);
-        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength);
-        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
-                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index);
-        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_BEFORE_N_BEST_CHOOSE, before);
-        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_AFTER_N_BEST_CHOOSE, after);
-        mContext.sendBroadcast(i);
-    }
-
-    public void inputEnded() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED));
-    }
-    
-    public void voiceInputSettingEnabled() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_ENABLED));
-    }
-    
-    public void voiceInputSettingDisabled() {
-        setHasLoggingInfo(true);
-        mContext.sendBroadcast(newLoggingBroadcast(
-                LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_DISABLED));
-    }
-
-    private void setHasLoggingInfo(boolean hasLoggingInfo) {
-        mHasLoggingInfo = hasLoggingInfo;
-        // If applications that call UserHappinessSignals.userAcceptedImeText
-        // make that call after VoiceInputLogger.flush() calls this method with false, we
-        // will lose those happiness signals. For example, consider the gmail sequence:
-        // 1. compose message
-        // 2. speak message into message field
-        // 3. type subject into subject field
-        // 4. press send
-        // We will NOT get the signal that the user accepted the voice inputted message text
-        // because when the user tapped on the subject field, the ime's flush will be triggered
-        // and the hasLoggingInfo will be then set to false. So by the time the user hits send
-        // we have essentially forgotten about any voice input.
-        // However the following (more common) use case is properly logged
-        // 1. compose message
-        // 2. type subject in subject field
-        // 3. speak message in message field
-        // 4. press send
-        VoiceInputLoggerCompatUtils.setHasVoiceLoggingInfoCompat(hasLoggingInfo);
-    }
-
-    private boolean hasLoggingInfo(){
-        return mHasLoggingInfo;
-    }
-
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
deleted file mode 100644
index 8ed279f..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2008-2009 The Android Open Source Project
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-
-/**
- * Utility class to draw a waveform into a bitmap, given a byte array
- * that represents the waveform as a sequence of 16-bit integers.
- * Adapted from RecognitionActivity.java.
- */
-public class WaveformImage {
-    private static final int SAMPLING_RATE = 8000;
-
-    private WaveformImage() {
-        // Intentional empty constructor.
-    }
-
-    public static Bitmap drawWaveform(
-        ByteArrayOutputStream waveBuffer, int w, int h, int start, int end) {
-        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
-        final Canvas c = new Canvas(b);
-        final Paint paint = new Paint();
-        paint.setColor(0xFFFFFFFF); // 0xRRGGBBAA
-        paint.setAntiAlias(true);
-        paint.setStrokeWidth(0);
-
-        final ShortBuffer buf = ByteBuffer
-            .wrap(waveBuffer.toByteArray())
-            .order(ByteOrder.nativeOrder())
-            .asShortBuffer();
-        buf.position(0);
-
-        final int numSamples = waveBuffer.size() / 2;
-        final int delay = (SAMPLING_RATE * 100 / 1000);
-        int endIndex = end / 2 + delay;
-        if (end == 0 || endIndex >= numSamples) {
-            endIndex = numSamples;
-        }
-        int index = start / 2 - delay;
-        if (index < 0) {
-            index = 0;
-        }
-        final int size = endIndex - index;
-        int numSamplePerPixel = 32;
-        int delta = size / (numSamplePerPixel * w);
-        if (delta == 0) {
-            numSamplePerPixel = size / w;
-            delta = 1;
-        }
-
-        final float scale = 3.5f / 65536.0f;
-        // do one less column to make sure we won't read past
-        // the buffer.
-        try {
-            for (int i = 0; i < w - 1 ; i++) {
-                final float x = i;
-                for (int j = 0; j < numSamplePerPixel; j++) {
-                    final short s = buf.get(index);
-                    final float y = (h / 2) - (s * h * scale);
-                    c.drawPoint(x, y, paint);
-                    index += delta;
-                }
-            }
-        } catch (IndexOutOfBoundsException e) {
-            // this can happen, but we don't care
-        }
-
-        return b;
-    }
-}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
deleted file mode 100644
index 6c5f52a..0000000
--- a/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.deprecated.voice;
-
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A set of text fields where speech has been explicitly enabled.
- */
-public class Whitelist {
-    private List<Bundle> mConditions;
-
-    public Whitelist() {
-        mConditions = new ArrayList<Bundle>();
-    }
-
-    public Whitelist(List<Bundle> conditions) {
-        this.mConditions = conditions;
-    }
-
-    public void addApp(String app) {
-        Bundle bundle = new Bundle();
-        bundle.putString("packageName", app);
-        mConditions.add(bundle);
-    }
-
-    /**
-     * @return true if the field is a member of the whitelist.
-     */
-    public boolean matches(FieldContext context) {
-        for (Bundle condition : mConditions) {
-            if (matches(condition, context.getBundle())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return true of all values in condition are matched by a value
-     *     in target.
-     */
-    private boolean matches(Bundle condition, Bundle target) {
-        for (String key : condition.keySet()) {
-          if (!condition.getString(key).equals(target.getString(key))) {
-            return false;
-          }
-        }
-        return true;
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f1ae0b3..0a2b010 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -22,56 +22,66 @@
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Xml;
 
+import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.keyboard.internal.KeyStyles;
 import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder.ParseException;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.keyboard.internal.MoreKeySpecParser;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Arrays;
 
 /**
  * Class for describing the position and characteristics of a single key in the keyboard.
  */
 public class Key {
+    private static final String TAG = Key.class.getSimpleName();
+
     /**
      * The key code (unicode or custom code) that this key generates.
      */
     public final int mCode;
+    public final int mAltCode;
 
     /** Label to display */
-    public final CharSequence mLabel;
+    public final String mLabel;
     /** Hint label to display on the key in conjunction with the label */
-    public final CharSequence mHintLabel;
-    /** Option of the label */
-    private final int mLabelOption;
-    private static final int LABEL_OPTION_ALIGN_LEFT = 0x01;
-    private static final int LABEL_OPTION_ALIGN_RIGHT = 0x02;
-    private static final int LABEL_OPTION_ALIGN_LEFT_OF_CENTER = 0x08;
-    private static final int LABEL_OPTION_LARGE_LETTER = 0x10;
-    private static final int LABEL_OPTION_FONT_NORMAL = 0x20;
-    private static final int LABEL_OPTION_FONT_MONO_SPACE = 0x40;
-    private static final int LABEL_OPTION_FOLLOW_KEY_LETTER_RATIO = 0x80;
-    private static final int LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100;
-    private static final int LABEL_OPTION_HAS_POPUP_HINT = 0x200;
-    private static final int LABEL_OPTION_HAS_UPPERCASE_LETTER = 0x400;
-    private static final int LABEL_OPTION_HAS_HINT_LABEL = 0x800;
-    private static final int LABEL_OPTION_WITH_ICON_LEFT = 0x1000;
-    private static final int LABEL_OPTION_WITH_ICON_RIGHT = 0x2000;
-    private static final int LABEL_OPTION_AUTO_X_SCALE = 0x4000;
+    public final String mHintLabel;
+    /** Flags of the label */
+    private final int mLabelFlags;
+    private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01;
+    private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02;
+    private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08;
+    private static final int LABEL_FLAGS_LARGE_LETTER = 0x10;
+    private static final int LABEL_FLAGS_FONT_NORMAL = 0x20;
+    private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x40;
+    public static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
+    private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100;
+    private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
+    private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
+    private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
+    private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
+    private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
+    private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
+    private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000;
+    private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000;
+    private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x20000;
+    private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
+    private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
 
     /** Icon to display instead of a label. Icon takes precedence over a label */
-    private Drawable mIcon;
+    private final int mIconId;
+    /** Icon for disabled state */
+    private final int mDisabledIconId;
     /** Preview version of the icon, for the preview popup */
-    private Drawable mPreviewIcon;
+    private final int mPreviewIconId;
 
     /** Width of the key, not including the gap */
     public final int mWidth;
@@ -94,106 +104,83 @@
     /** Text to output when pressed. This can be multiple characters, like ".com" */
     public final CharSequence mOutputText;
     /** More keys */
-    public final CharSequence[] mMoreKeys;
-    /** More keys maximum column number */
-    public final int mMaxMoreKeysColumn;
+    public final String[] mMoreKeys;
+    /** More keys column number and flags */
+    private final int mMoreKeysColumnAndFlags;
+    private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff;
+    private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
+    private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
+    private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
+    private static final int MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY = 0x10000000;
+    private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
+    private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
+    private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
+    private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
+    private static final String MORE_KEYS_EMBEDDED_MORE_KEY = "!embeddedMoreKey!";
 
     /** Background type that represents different key background visual than normal one. */
     public final int mBackgroundType;
     public static final int BACKGROUND_TYPE_NORMAL = 0;
     public static final int BACKGROUND_TYPE_FUNCTIONAL = 1;
     public static final int BACKGROUND_TYPE_ACTION = 2;
-    public static final int BACKGROUND_TYPE_STICKY = 3;
+    public static final int BACKGROUND_TYPE_STICKY_OFF = 3;
+    public static final int BACKGROUND_TYPE_STICKY_ON = 4;
 
-    /** Whether this key repeats itself when held down */
-    public final boolean mRepeatable;
+    private final int mActionFlags;
+    private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
+    private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
+    private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
+    private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
+
+    private final int mHashCode;
 
     /** The current pressed state of this key */
     private boolean mPressed;
-    /** If this is a sticky key, is its highlight on? */
-    private boolean mHighlightOn;
     /** Key is enabled and responds on press */
     private boolean mEnabled = true;
-    /** Whether this key needs to show the "..." popup hint for special purposes */
-    private boolean mNeedsSpecialPopupHint;
-
-    // RTL parenthesis character swapping map.
-    private static final Map<Integer, Integer> sRtlParenthesisMap = new HashMap<Integer, Integer>();
-
-    static {
-        // The all letters need to be mirrored are found at
-        // http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt
-        addRtlParenthesisPair('(', ')');
-        addRtlParenthesisPair('[', ']');
-        addRtlParenthesisPair('{', '}');
-        addRtlParenthesisPair('<', '>');
-        // \u00ab: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-        // \u00bb: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        addRtlParenthesisPair('\u00ab', '\u00bb');
-        // \u2039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK
-        // \u203a: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        addRtlParenthesisPair('\u2039', '\u203a');
-        // \u2264: LESS-THAN OR EQUAL TO
-        // \u2265: GREATER-THAN OR EQUAL TO
-        addRtlParenthesisPair('\u2264', '\u2265');
-    }
-
-    private static void addRtlParenthesisPair(int left, int right) {
-        sRtlParenthesisMap.put(left, right);
-        sRtlParenthesisMap.put(right, left);
-    }
-
-    public static int getRtlParenthesisCode(int code, boolean isRtl) {
-        if (isRtl && sRtlParenthesisMap.containsKey(code)) {
-            return sRtlParenthesisMap.get(code);
-        } else {
-            return code;
-        }
-    }
-
-    private static int getCode(Resources res, KeyboardParams params, String moreKeySpec) {
-        return getRtlParenthesisCode(
-                MoreKeySpecParser.getCode(res, moreKeySpec), params.mIsRtlKeyboard);
-    }
-
-    private static Drawable getIcon(KeyboardParams params, String moreKeySpec) {
-        return params.mIconsSet.getIcon(MoreKeySpecParser.getIconId(moreKeySpec));
-    }
 
     /**
      * This constructor is being used only for key in more keys keyboard.
      */
-    public Key(Resources res, KeyboardParams params, String moreKeySpec,
-            int x, int y, int width, int height) {
-        this(params, MoreKeySpecParser.getLabel(moreKeySpec), null, getIcon(params, moreKeySpec),
-                getCode(res, params, moreKeySpec), MoreKeySpecParser.getOutputText(moreKeySpec),
-                x, y, width, height);
+    public Key(Resources res, Keyboard.Params params, String moreKeySpec,
+            int x, int y, int width, int height, int labelFlags) {
+        this(params, KeySpecParser.getLabel(moreKeySpec), null,
+                KeySpecParser.getIconId(moreKeySpec),
+                KeySpecParser.getCode(res, moreKeySpec),
+                KeySpecParser.getOutputText(moreKeySpec),
+                x, y, width, height, labelFlags);
     }
 
     /**
      * This constructor is being used only for key in popup suggestions pane.
      */
-    public Key(KeyboardParams params, CharSequence label, CharSequence hintLabel, Drawable icon,
-            int code, CharSequence outputText, int x, int y, int width, int height) {
+    public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
+            int code, String outputText, int x, int y, int width, int height, int labelFlags) {
         mHeight = height - params.mVerticalGap;
         mHorizontalGap = params.mHorizontalGap;
         mVerticalGap = params.mVerticalGap;
         mVisualInsetsLeft = mVisualInsetsRight = 0;
         mWidth = width - mHorizontalGap;
         mHintLabel = hintLabel;
-        mLabelOption = 0;
+        mLabelFlags = labelFlags;
         mBackgroundType = BACKGROUND_TYPE_NORMAL;
-        mRepeatable = false;
+        mActionFlags = 0;
         mMoreKeys = null;
-        mMaxMoreKeysColumn = 0;
+        mMoreKeysColumnAndFlags = 0;
         mLabel = label;
         mOutputText = outputText;
         mCode = code;
-        mIcon = icon;
+        mEnabled = (code != Keyboard.CODE_UNSPECIFIED);
+        mAltCode = Keyboard.CODE_UNSPECIFIED;
+        mIconId = iconId;
+        mDisabledIconId = KeyboardIconsSet.ICON_UNDEFINED;
+        mPreviewIconId = KeyboardIconsSet.ICON_UNDEFINED;
         // Horizontal gap is divided equally to both sides of the key.
         mX = x + mHorizontalGap / 2;
         mY = y;
         mHitBox.set(x, y, x + width + 1, y + height);
+
+        mHashCode = computeHashCode(this);
     }
 
     /**
@@ -205,9 +192,10 @@
      *        this key.
      * @param parser the XML parser containing the attributes for this key
      * @param keyStyles active key styles set
+     * @throws XmlPullParserException
      */
-    public Key(Resources res, KeyboardParams params, KeyboardBuilder.Row row,
-            XmlPullParser parser, KeyStyles keyStyles) {
+    public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
+            XmlPullParser parser, KeyStyles keyStyles) throws XmlPullParserException {
         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
         final int keyHeight = row.mRowHeight;
         mVerticalGap = params.mVerticalGap;
@@ -221,9 +209,10 @@
             String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
             style = keyStyles.getKeyStyle(styleName);
             if (style == null)
-                throw new ParseException("Unknown key style: " + styleName, parser);
+                throw new XmlParseUtils.ParseException(
+                        "Unknown key style: " + styleName, parser);
         } else {
-            style = keyStyles.getEmptyKeyStyle();
+            style = KeyStyles.getEmptyKeyStyle();
         }
 
         final float keyXPos = row.getKeyX(keyAttr);
@@ -239,89 +228,267 @@
         // Update row to have current x coordinate.
         row.setXPos(keyXPos + keyWidth);
 
-        final CharSequence[] moreKeys = style.getTextArray(keyAttr,
-                R.styleable.Keyboard_Key_moreKeys);
-        // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of
-        // config_digit_more_keys_enabled.
-        if (params.mId.isAlphabetKeyboard()
-                && !res.getBoolean(R.bool.config_digit_more_keys_enabled)) {
-            mMoreKeys = MoreKeySpecParser.filterOut(res, moreKeys, MoreKeySpecParser.DIGIT_FILTER);
-        } else {
-            mMoreKeys = moreKeys;
-        }
-        mMaxMoreKeysColumn = style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMiniKeyboardColumn);
-
         mBackgroundType = style.getInt(keyAttr,
                 R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
-        mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
-        mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
 
-        final KeyboardIconsSet iconsSet = params.mIconsSet;
-        mVisualInsetsLeft = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr,
+        mVisualInsetsLeft = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
                 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0);
-        mVisualInsetsRight = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr,
+        mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
                 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0);
-        mPreviewIcon = iconsSet.getIcon(style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED));
-        mIcon = iconsSet.getIcon(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIcon,
-                KeyboardIconsSet.ICON_UNDEFINED));
-        final int shiftedIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted,
-                KeyboardIconsSet.ICON_UNDEFINED);
-        if (shiftedIconId != KeyboardIconsSet.ICON_UNDEFINED) {
-            final Drawable shiftedIcon = iconsSet.getIcon(shiftedIconId);
-            params.addShiftedIcon(this, shiftedIcon);
-        }
-        mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
+        mPreviewIconId = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED);
+        mIconId = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED);
+        mDisabledIconId = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED);
 
-        mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
-        mLabelOption = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption, 0);
-        mOutputText = style.getText(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
-        // Choose the first letter of the label as primary code if not
-        // specified.
-        final int code = style.getInt(keyAttr, R.styleable.Keyboard_Key_code,
-                Keyboard.CODE_UNSPECIFIED);
-        if (code == Keyboard.CODE_UNSPECIFIED && !TextUtils.isEmpty(mLabel)) {
-            final int firstChar = mLabel.charAt(0);
-            mCode = getRtlParenthesisCode(firstChar, params.mIsRtlKeyboard);
-        } else if (code != Keyboard.CODE_UNSPECIFIED) {
-            mCode = code;
-        } else {
-            mCode = Keyboard.CODE_DUMMY;
+        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
+                | row.getDefaultKeyLabelFlags();
+        final boolean preserveCase = (mLabelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0;
+        int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
+        String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
+
+        int moreKeysColumn = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
+        int value;
+        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
+            moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
         }
+        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
+            moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
+        }
+        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
+            moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS;
+        }
+        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
+            moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
+        }
+        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) {
+            moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY;
+        }
+        mMoreKeysColumnAndFlags = moreKeysColumn;
+
+        final String[] additionalMoreKeys;
+        if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
+            additionalMoreKeys = null;
+        } else {
+            additionalMoreKeys = style.getStringArray(
+                    keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
+        }
+        moreKeys = KeySpecParser.insertAddtionalMoreKeys(moreKeys, additionalMoreKeys);
+        if (moreKeys != null) {
+            actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
+            for (int i = 0; i < moreKeys.length; i++) {
+                moreKeys[i] = adjustCaseOfStringForKeyboardId(
+                        moreKeys[i], preserveCase, params.mId);
+            }
+        }
+        mActionFlags = actionFlags;
+        mMoreKeys = moreKeys;
+
+        if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
+            mLabel = params.mId.mCustomActionLabel;
+        } else {
+            mLabel = adjustCaseOfStringForKeyboardId(style.getString(
+                    keyAttr, R.styleable.Keyboard_Key_keyLabel), preserveCase, params.mId);
+        }
+        if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
+            mHintLabel = null;
+        } else {
+            mHintLabel = adjustCaseOfStringForKeyboardId(style.getString(
+                    keyAttr, R.styleable.Keyboard_Key_keyHintLabel), preserveCase, params.mId);
+        }
+        String outputText = adjustCaseOfStringForKeyboardId(style.getString(
+                keyAttr, R.styleable.Keyboard_Key_keyOutputText), preserveCase, params.mId);
+        final int code = style.getInt(
+                keyAttr, R.styleable.Keyboard_Key_code, Keyboard.CODE_UNSPECIFIED);
+        // Choose the first letter of the label as primary code if not specified.
+        if (code == Keyboard.CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
+                && !TextUtils.isEmpty(mLabel)) {
+            if (StringUtils.codePointCount(mLabel) == 1) {
+                // Use the first letter of the hint label if shiftedLetterActivated flag is
+                // specified.
+                if (hasShiftedLetterHint() && isShiftedLetterActivated()
+                        && !TextUtils.isEmpty(mHintLabel)) {
+                    mCode = mHintLabel.codePointAt(0);
+                } else {
+                    mCode = mLabel.codePointAt(0);
+                }
+            } else {
+                // In some locale and case, the character might be represented by multiple code
+                // points, such as upper case Eszett of German alphabet.
+                outputText = mLabel;
+                mCode = Keyboard.CODE_OUTPUT_TEXT;
+            }
+        } else if (code == Keyboard.CODE_UNSPECIFIED && outputText != null) {
+            if (StringUtils.codePointCount(outputText) == 1) {
+                mCode = outputText.codePointAt(0);
+                outputText = null;
+            } else {
+                mCode = Keyboard.CODE_OUTPUT_TEXT;
+            }
+        } else {
+            mCode = adjustCaseOfCodeForKeyboardId(code, preserveCase, params.mId);
+        }
+        mOutputText = outputText;
+        mAltCode = adjustCaseOfCodeForKeyboardId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED), preserveCase,
+                params.mId);
+        mHashCode = computeHashCode(this);
 
         keyAttr.recycle();
+
+        if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
+            Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
+        }
     }
 
-    public void markAsLeftEdge(KeyboardParams params) {
+    private static int adjustCaseOfCodeForKeyboardId(int code, boolean preserveCase,
+            KeyboardId id) {
+        if (!Keyboard.isLetterCode(code) || preserveCase) return code;
+        final String text = new String(new int[] { code } , 0, 1);
+        final String casedText = adjustCaseOfStringForKeyboardId(text, preserveCase, id);
+        return StringUtils.codePointCount(casedText) == 1
+                ? casedText.codePointAt(0) : Keyboard.CODE_UNSPECIFIED;
+    }
+
+    private static String adjustCaseOfStringForKeyboardId(String text, boolean preserveCase,
+            KeyboardId id) {
+        if (text == null || preserveCase) return text;
+        switch (id.mElementId) {
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+            return text.toUpperCase(id.mLocale);
+        default:
+            return text;
+        }
+    }
+
+    private static int computeHashCode(Key key) {
+        return Arrays.hashCode(new Object[] {
+                key.mX,
+                key.mY,
+                key.mWidth,
+                key.mHeight,
+                key.mCode,
+                key.mLabel,
+                key.mHintLabel,
+                key.mIconId,
+                key.mBackgroundType,
+                Arrays.hashCode(key.mMoreKeys),
+                key.mOutputText,
+                key.mActionFlags,
+                key.mLabelFlags,
+                // Key can be distinguishable without the following members.
+                // key.mAltCode,
+                // key.mDisabledIconId,
+                // key.mPreviewIconId,
+                // key.mHorizontalGap,
+                // key.mVerticalGap,
+                // key.mVisualInsetLeft,
+                // key.mVisualInsetRight,
+                // key.mMaxMoreKeysColumn,
+        });
+    }
+
+    private boolean equals(Key o) {
+        if (this == o) return true;
+        return o.mX == mX
+                && o.mY == mY
+                && o.mWidth == mWidth
+                && o.mHeight == mHeight
+                && o.mCode == mCode
+                && TextUtils.equals(o.mLabel, mLabel)
+                && TextUtils.equals(o.mHintLabel, mHintLabel)
+                && o.mIconId == mIconId
+                && o.mBackgroundType == mBackgroundType
+                && Arrays.equals(o.mMoreKeys, mMoreKeys)
+                && TextUtils.equals(o.mOutputText, mOutputText)
+                && o.mActionFlags == mActionFlags
+                && o.mLabelFlags == mLabelFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        return mHashCode;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof Key && equals((Key)o);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s/%s %d,%d %dx%d %s/%s/%s",
+                Keyboard.printableCode(mCode), mLabel, mX, mY, mWidth, mHeight, mHintLabel,
+                KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
+    }
+
+    private static String backgroundName(int backgroundType) {
+        switch (backgroundType) {
+        case BACKGROUND_TYPE_NORMAL: return "normal";
+        case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
+        case BACKGROUND_TYPE_ACTION: return "action";
+        case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
+        case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
+        default: return null;
+        }
+    }
+
+    public void markAsLeftEdge(Keyboard.Params params) {
         mHitBox.left = params.mHorizontalEdgesPadding;
     }
 
-    public void markAsRightEdge(KeyboardParams params) {
+    public void markAsRightEdge(Keyboard.Params params) {
         mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
     }
 
-    public void markAsTopEdge(KeyboardParams params) {
+    public void markAsTopEdge(Keyboard.Params params) {
         mHitBox.top = params.mTopPadding;
     }
 
-    public void markAsBottomEdge(KeyboardParams params) {
+    public void markAsBottomEdge(Keyboard.Params params) {
         mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
     }
 
-    public boolean isSticky() {
-        return mBackgroundType == BACKGROUND_TYPE_STICKY;
+    public final boolean isSpacer() {
+        return this instanceof Spacer;
     }
 
-    public boolean isSpacer() {
-        return false;
+    public boolean isShift() {
+        return mCode == Keyboard.CODE_SHIFT;
+    }
+
+    public boolean isModifier() {
+        return mCode == Keyboard.CODE_SHIFT || mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
+    }
+
+    public boolean isRepeatable() {
+        return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
+    }
+
+    public boolean noKeyPreview() {
+        return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
+    }
+
+    public boolean altCodeWhileTyping() {
+        return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
+    }
+
+    public boolean isLongPressEnabled() {
+        // We need not start long press timer on the key which has activated shifted letter.
+        return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
+                && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
     }
 
     public Typeface selectTypeface(Typeface defaultTypeface) {
         // TODO: Handle "bold" here too?
-        if ((mLabelOption & LABEL_OPTION_FONT_NORMAL) != 0) {
+        if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
             return Typeface.DEFAULT;
-        } else if ((mLabelOption & LABEL_OPTION_FONT_MONO_SPACE) != 0) {
+        } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
             return Typeface.MONOSPACE;
         } else {
             return defaultTypeface;
@@ -329,13 +496,13 @@
     }
 
     public int selectTextSize(int letter, int largeLetter, int label, int hintLabel) {
-        if (mLabel.length() > 1
-                && (mLabelOption & (LABEL_OPTION_FOLLOW_KEY_LETTER_RATIO
-                        | LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
+        if (StringUtils.codePointCount(mLabel) > 1
+                && (mLabelFlags & (LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
+                        | LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
             return label;
-        } else if ((mLabelOption & LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) {
+        } else if ((mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) {
             return hintLabel;
-        } else if ((mLabelOption & LABEL_OPTION_LARGE_LETTER) != 0) {
+        } else if ((mLabelFlags & LABEL_FLAGS_LARGE_LETTER) != 0) {
             return largeLetter;
         } else {
             return letter;
@@ -343,63 +510,74 @@
     }
 
     public boolean isAlignLeft() {
-        return (mLabelOption & LABEL_OPTION_ALIGN_LEFT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
     }
 
     public boolean isAlignRight() {
-        return (mLabelOption & LABEL_OPTION_ALIGN_RIGHT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
     }
 
     public boolean isAlignLeftOfCenter() {
-        return (mLabelOption & LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0;
+        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
     }
 
     public boolean hasPopupHint() {
-        return (mLabelOption & LABEL_OPTION_HAS_POPUP_HINT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
     }
 
-    public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) {
-        mNeedsSpecialPopupHint = needsSpecialPopupHint;
-    }
-
-    public boolean needsSpecialPopupHint() {
-        return mNeedsSpecialPopupHint;
-    }
-
-    public boolean hasUppercaseLetter() {
-        return (mLabelOption & LABEL_OPTION_HAS_UPPERCASE_LETTER) != 0;
+    public boolean hasShiftedLetterHint() {
+        return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
     }
 
     public boolean hasHintLabel() {
-        return (mLabelOption & LABEL_OPTION_HAS_HINT_LABEL) != 0;
+        return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
     }
 
     public boolean hasLabelWithIconLeft() {
-        return (mLabelOption & LABEL_OPTION_WITH_ICON_LEFT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
     }
 
     public boolean hasLabelWithIconRight() {
-        return (mLabelOption & LABEL_OPTION_WITH_ICON_RIGHT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
     }
 
     public boolean needsXScale() {
-        return (mLabelOption & LABEL_OPTION_AUTO_X_SCALE) != 0;
+        return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
     }
 
-    public Drawable getIcon() {
-        return mIcon;
+    public boolean isShiftedLetterActivated() {
+        return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
     }
 
-    public Drawable getPreviewIcon() {
-        return mPreviewIcon;
+    public int getMoreKeysColumn() {
+        return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
     }
 
-    public void setIcon(Drawable icon) {
-        mIcon = icon;
+    public boolean isFixedColumnOrderMoreKeys() {
+        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
     }
 
-    public void setPreviewIcon(Drawable icon) {
-        mPreviewIcon = icon;
+    public boolean hasLabelsInMoreKeys() {
+        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
+    }
+
+    public boolean needsDividersInMoreKeys() {
+        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
+    }
+
+    public boolean hasEmbeddedMoreKey() {
+        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
+    }
+
+    public Drawable getIcon(KeyboardIconsSet iconSet) {
+        final int iconId = mEnabled ? mIconId : mDisabledIconId;
+        return iconSet.getIconDrawable(iconId);
+    }
+
+    public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
+        return mPreviewIconId != KeyboardIconsSet.ICON_UNDEFINED
+                ? iconSet.getIconDrawable(mPreviewIconId)
+                : iconSet.getIconDrawable(mIconId);
     }
 
     /**
@@ -420,10 +598,6 @@
         mPressed = false;
     }
 
-    public void setHighlightOn(boolean highlightOn) {
-        mHighlightOn = highlightOn;
-    }
-
     public boolean isEnabled() {
         return mEnabled;
     }
@@ -436,9 +610,9 @@
      * Detects if a point falls on this key.
      * @param x the x-coordinate of the point
      * @param y the y-coordinate of the point
-     * @return whether or not the point falls on the key. If the key is attached to an edge, it will
-     * assume that all points between the key and the edge are considered to be on the key.
-     * @see {@link #markAsLeftEdge(KeyboardParams)} etc.
+     * @return whether or not the point falls on the key. If the key is attached to an edge, it
+     * will assume that all points between the key and the edge are considered to be on the key.
+     * @see #markAsLeftEdge(Keyboard.Params) etc.
      */
     public boolean isOnKey(int x, int y) {
         return mHitBox.contains(x, y);
@@ -517,40 +691,32 @@
      * @see android.graphics.drawable.StateListDrawable#setState(int[])
      */
     public int[] getCurrentDrawableState() {
-        final boolean pressed = mPressed;
-
         switch (mBackgroundType) {
         case BACKGROUND_TYPE_FUNCTIONAL:
-            return pressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
+            return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
         case BACKGROUND_TYPE_ACTION:
-            return pressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
-        case BACKGROUND_TYPE_STICKY:
-            if (mHighlightOn) {
-                return pressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
-            } else {
-                return pressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
-            }
+            return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
+        case BACKGROUND_TYPE_STICKY_OFF:
+            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
+        case BACKGROUND_TYPE_STICKY_ON:
+            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
         default: /* BACKGROUND_TYPE_NORMAL */
-            return pressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
+            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
         }
     }
 
     public static class Spacer extends Key {
-        public Spacer(Resources res, KeyboardParams params, KeyboardBuilder.Row row,
-                XmlPullParser parser, KeyStyles keyStyles) {
+        public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
+                XmlPullParser parser, KeyStyles keyStyles) throws XmlPullParserException {
             super(res, params, row, parser, keyStyles);
         }
 
         /**
          * This constructor is being used only for divider in more keys keyboard.
          */
-        public Spacer(KeyboardParams params, Drawable icon, int x, int y, int width, int height) {
-            super(params, null, null, icon, Keyboard.CODE_DUMMY, null, x, y, width, height);
-        }
-
-        @Override
-        public boolean isSpacer() {
-            return true;
+        protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
+            super(params, null, null, KeyboardIconsSet.ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED,
+                    null, x, y, width, height, 0);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 3298c41..13e909c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,17 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.List;
 
 public class KeyDetector {
-    private static final String TAG = KeyDetector.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
     public static final int NOT_A_CODE = -1;
-    public static final int NOT_A_KEY = -1;
 
     private final int mKeyHysteresisDistanceSquared;
 
@@ -34,12 +26,6 @@
     private int mCorrectionX;
     private int mCorrectionY;
     private boolean mProximityCorrectOn;
-    private int mProximityThresholdSquare;
-
-    // working area
-    private static final int MAX_NEARBY_KEYS = 12;
-    private final int[] mDistances = new int[MAX_NEARBY_KEYS];
-    private final int[] mIndices = new int[MAX_NEARBY_KEYS];
 
     /**
      * This class handles key detection.
@@ -57,19 +43,17 @@
         mCorrectionX = (int)correctionX;
         mCorrectionY = (int)correctionY;
         mKeyboard = keyboard;
-        final int threshold = keyboard.mMostCommonKeyWidth;
-        mProximityThresholdSquare = threshold * threshold;
     }
 
     public int getKeyHysteresisDistanceSquared() {
         return mKeyHysteresisDistanceSquared;
     }
 
-    protected int getTouchX(int x) {
+    public int getTouchX(int x) {
         return x + mCorrectionX;
     }
 
-    protected int getTouchY(int y) {
+    public int getTouchY(int y) {
         return y + mCorrectionY;
     }
 
@@ -87,137 +71,49 @@
         return mProximityCorrectOn;
     }
 
-    public void setProximityThreshold(int threshold) {
-        mProximityThresholdSquare = threshold * threshold;
-    }
-
     public boolean alwaysAllowsSlidingInput() {
         return false;
     }
 
     /**
-     * Computes maximum size of the array that can contain all nearby key indices returned by
-     * {@link #getKeyIndexAndNearbyCodes}.
-     *
-     * @return Returns maximum size of the array that can contain all nearby key indices returned
-     *         by {@link #getKeyIndexAndNearbyCodes}.
-     */
-    protected int getMaxNearbyKeys() {
-        return MAX_NEARBY_KEYS;
-    }
-
-    /**
-     * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
-     * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
-     *
-     * @return Allocates and returns an array that can hold all key indices returned by
-     *         {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
-     *         initialized by {@link #NOT_A_CODE} value.
-     */
-    public int[] newCodeArray() {
-        int[] codes = new int[getMaxNearbyKeys()];
-        Arrays.fill(codes, NOT_A_CODE);
-        return codes;
-    }
-
-    private void initializeNearbyKeys() {
-        Arrays.fill(mDistances, Integer.MAX_VALUE);
-        Arrays.fill(mIndices, NOT_A_KEY);
-    }
-
-    /**
-     * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
-     * If the distance of two keys are the same, the key which the point is on should be considered
-     * as a closer one.
-     *
-     * @param keyIndex index of the key.
-     * @param distance distance between the key's edge and user touched point.
-     * @param isOnKey true if the point is on the key.
-     * @return order of the key in the nearby buffer, 0 if it is the nearest key.
-     */
-    private int sortNearbyKeys(int keyIndex, int distance, boolean isOnKey) {
-        final int[] distances = mDistances;
-        final int[] indices = mIndices;
-        for (int insertPos = 0; insertPos < distances.length; insertPos++) {
-            final int comparingDistance = distances[insertPos];
-            if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
-                final int nextPos = insertPos + 1;
-                if (nextPos < distances.length) {
-                    System.arraycopy(distances, insertPos, distances, nextPos,
-                            distances.length - nextPos);
-                    System.arraycopy(indices, insertPos, indices, nextPos,
-                            indices.length - nextPos);
-                }
-                distances[insertPos] = distance;
-                indices[insertPos] = keyIndex;
-                return insertPos;
-            }
-        }
-        return distances.length;
-    }
-
-    private void getNearbyKeyCodes(final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
-        final int[] indices = mIndices;
-
-        // allCodes[0] should always have the key code even if it is a non-letter key.
-        if (indices[0] == NOT_A_KEY) {
-            allCodes[0] = NOT_A_CODE;
-            return;
-        }
-
-        int numCodes = 0;
-        for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
-            final int index = indices[j];
-            if (index == NOT_A_KEY)
-                break;
-            final int code = keys.get(index).mCode;
-            // filter out a non-letter key from nearby keys
-            if (code < Keyboard.CODE_SPACE)
-                continue;
-            allCodes[numCodes++] = code;
-        }
-    }
-
-    /**
-     * Finds all possible nearby key indices around a touch event point and returns the nearest key
-     * index. The algorithm to determine the nearby keys depends on the threshold set by
-     * {@link #setProximityThreshold(int)} and the mode set by
-     * {@link #setProximityCorrectionEnabled(boolean)}.
+     * Detect the key whose hitbox the touch point is in.
      *
      * @param x The x-coordinate of a touch point
      * @param y The y-coordinate of a touch point
-     * @param allCodes All nearby key code except functional key are returned in this array
-     * @return The nearest key index
+     * @return the key that the touch point hits.
      */
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    public Key detectHitKey(int x, int y) {
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
-        initializeNearbyKeys();
-        int primaryIndex = NOT_A_KEY;
-        for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
-            final Key key = keys.get(index);
+        int minDistance = Integer.MAX_VALUE;
+        Key primaryKey = null;
+        for (final Key key: mKeyboard.getNearestKeys(touchX, touchY)) {
             final boolean isOnKey = key.isOnKey(touchX, touchY);
             final int distance = key.squaredDistanceToEdge(touchX, touchY);
-            if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
-                final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
-                if (insertedPosition == 0 && isOnKey)
-                    primaryIndex = index;
+            // To take care of hitbox overlaps, we compare mCode here too.
+            if (primaryKey == null || distance < minDistance
+                    || (distance == minDistance && isOnKey && key.mCode > primaryKey.mCode)) {
+                minDistance = distance;
+                primaryKey = key;
             }
         }
+        return primaryKey;
+    }
 
-        if (allCodes != null && allCodes.length > 0) {
-            getNearbyKeyCodes(allCodes);
-            if (DEBUG) {
-                Log.d(TAG, "x=" + x + " y=" + y
-                        + " primary="
-                        + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
-                        + " codes=" + Arrays.toString(allCodes));
-            }
+    public static String printableCode(Key key) {
+        return key != null ? Keyboard.printableCode(key.mCode) : "none";
+    }
+
+    public static String printableCodes(int[] codes) {
+        final StringBuilder sb = new StringBuilder();
+        boolean addDelimiter = false;
+        for (final int code : codes) {
+            if (code == NOT_A_CODE) break;
+            if (addDelimiter) sb.append(", ");
+            sb.append(Keyboard.printableCode(code));
+            addDelimiter = true;
         }
-
-        return primaryIndex;
+        return "[" + sb + "]";
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 4578507..67e4e4a 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,17 +16,32 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InflateException;
 
+import com.android.inputmethod.keyboard.internal.KeyStyles;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.XmlParseUtils;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -47,7 +62,11 @@
  * </pre>
  */
 public class Keyboard {
-    /** Some common keys code.  These should be aligned with values/keycodes.xml */
+    private static final String TAG = Keyboard.class.getSimpleName();
+
+    /** Some common keys code. Must be positive.
+     * These should be aligned with values/keycodes.xml
+     */
     public static final int CODE_ENTER = '\n';
     public static final int CODE_TAB = '\t';
     public static final int CODE_SPACE = ' ';
@@ -62,22 +81,23 @@
     public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
     public static final int CODE_CLOSING_CURLY_BRACKET = '}';
     public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
-    public static final int CODE_DIGIT0 = '0';
-    public static final int CODE_PLUS = '+';
+    private static final int MINIMUM_LETTER_CODE = CODE_TAB;
 
-
-    /** Special keys code.  These should be aligned with values/keycodes.xml */
-    public static final int CODE_DUMMY = 0;
+    /** Special keys code. Must be negative.
+     * These should be aligned with values/keycodes.xml
+     */
     public static final int CODE_SHIFT = -1;
     public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
-    public static final int CODE_CAPSLOCK = -3;
-    public static final int CODE_CANCEL = -4;
-    public static final int CODE_DELETE = -5;
-    public static final int CODE_SETTINGS = -6;
-    public static final int CODE_SHORTCUT = -7;
-    public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY = -98;
+    public static final int CODE_OUTPUT_TEXT = -3;
+    public static final int CODE_DELETE = -4;
+    public static final int CODE_SETTINGS = -5;
+    public static final int CODE_SHORTCUT = -6;
+    public static final int CODE_ACTION_ENTER = -7;
+    public static final int CODE_ACTION_NEXT = -8;
+    public static final int CODE_ACTION_PREVIOUS = -9;
+    public static final int CODE_LANGUAGE_SWITCH = -10;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -99;
+    public static final int CODE_UNSPECIFIED = -11;
 
     public final KeyboardId mId;
     public final int mThemeId;
@@ -98,159 +118,1195 @@
     /** More keys keyboard template */
     public final int mMoreKeysTemplate;
 
-    /** Maximum column for mini keyboard */
-    public final int mMaxMiniKeyboardColumn;
+    /** Maximum column for more keys keyboard */
+    public final int mMaxMoreKeysKeyboardColumn;
 
-    /** True if Right-To-Left keyboard */
-    public final boolean mIsRtlKeyboard;
-
-    /** List of keys and icons in this keyboard */
-    public final List<Key> mKeys;
-    public final List<Key> mShiftKeys;
-    public final Set<Key> mShiftLockKeys;
-    public final Map<Key, Drawable> mShiftedIcons;
-    public final Map<Key, Drawable> mUnshiftedIcons;
+    /** Array of keys and icons in this keyboard */
+    public final Key[] mKeys;
+    public final Key[] mShiftKeys;
+    public final Key[] mAltCodeKeysWhileTyping;
     public final KeyboardIconsSet mIconsSet;
 
-    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
+    private final HashMap<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
 
     private final ProximityInfo mProximityInfo;
+    private final boolean mProximityCharsCorrectionEnabled;
 
-    public Keyboard(KeyboardParams params) {
+    public Keyboard(Params params) {
         mId = params.mId;
         mThemeId = params.mThemeId;
         mOccupiedHeight = params.mOccupiedHeight;
         mOccupiedWidth = params.mOccupiedWidth;
         mMostCommonKeyHeight = params.mMostCommonKeyHeight;
         mMostCommonKeyWidth = params.mMostCommonKeyWidth;
-        mIsRtlKeyboard = params.mIsRtlKeyboard;
         mMoreKeysTemplate = params.mMoreKeysTemplate;
-        mMaxMiniKeyboardColumn = params.mMaxMiniKeyboardColumn;
+        mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
 
         mTopPadding = params.mTopPadding;
         mVerticalGap = params.mVerticalGap;
 
-        mKeys = Collections.unmodifiableList(params.mKeys);
-        mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
-        mShiftLockKeys = Collections.unmodifiableSet(params.mShiftLockKeys);
-        mShiftedIcons = Collections.unmodifiableMap(params.mShiftedIcons);
-        mUnshiftedIcons = Collections.unmodifiableMap(params.mUnshiftedIcons);
+        mKeys = params.mKeys.toArray(new Key[params.mKeys.size()]);
+        mShiftKeys = params.mShiftKeys.toArray(new Key[params.mShiftKeys.size()]);
+        mAltCodeKeysWhileTyping = params.mAltCodeKeysWhileTyping.toArray(
+                new Key[params.mAltCodeKeysWhileTyping.size()]);
         mIconsSet = params.mIconsSet;
 
-        mProximityInfo = new ProximityInfo(
+        mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(),
                 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
                 mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);
+        mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
+    }
+
+    public boolean hasProximityCharsCorrection(int code) {
+        if (!mProximityCharsCorrectionEnabled) {
+            return false;
+        }
+        // Note: The native code has the main keyboard layout only at this moment.
+        // TODO: Figure out how to handle proximity characters information of all layouts.
+        final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = (
+                mId.mElementId == KeyboardId.ELEMENT_ALPHABET
+                || mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);
+        return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);
     }
 
     public ProximityInfo getProximityInfo() {
         return mProximityInfo;
     }
 
-    public boolean hasShiftLockKey() {
-        return !mShiftLockKeys.isEmpty();
-    }
+    public Key getKey(int code) {
+        if (code == CODE_UNSPECIFIED) {
+            return null;
+        }
+        final Integer keyCode = code;
+        if (mKeyCache.containsKey(keyCode)) {
+            return mKeyCache.get(keyCode);
+        }
 
-    public boolean setShiftLocked(boolean newShiftLockState) {
-        for (final Key key : mShiftLockKeys) {
-            // To represent "shift locked" state. The highlight is handled by background image that
-            // might be a StateListDrawable.
-            key.setHighlightOn(newShiftLockState);
-            // To represent "shifted" state. The key might have a shifted icon.
-            if (newShiftLockState && mShiftedIcons.containsKey(key)) {
-                key.setIcon(mShiftedIcons.get(key));
-            } else {
-                key.setIcon(mUnshiftedIcons.get(key));
+        for (final Key key : mKeys) {
+            if (key.mCode == code) {
+                mKeyCache.put(keyCode, key);
+                return key;
             }
         }
-        mShiftState.setShiftLocked(newShiftLockState);
-        return true;
+        mKeyCache.put(keyCode, null);
+        return null;
     }
 
-    public boolean isShiftLocked() {
-        return mShiftState.isShiftLocked();
-    }
-
-    public boolean isShiftLockShifted() {
-        return mShiftState.isShiftLockShifted();
-    }
-
-    public boolean setShifted(boolean newShiftState) {
-        for (final Key key : mShiftKeys) {
-            if (!newShiftState && !mShiftState.isShiftLocked()) {
-                key.setIcon(mUnshiftedIcons.get(key));
-            } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) {
-                key.setIcon(mShiftedIcons.get(key));
-            }
-        }
-        return mShiftState.setShifted(newShiftState);
-    }
-
+    // TODO: Remove this method.
     public boolean isShiftedOrShiftLocked() {
-        return mShiftState.isShiftedOrShiftLocked();
+        // Alphabet mode have unshifted, manual shifted, automatic shifted, shift locked, and
+        // shift lock shifted element. So that unshifed element is the only one that is NOT in
+        // shifted or shift locked state.
+        return mId.isAlphabetKeyboard() && mId.mElementId != KeyboardId.ELEMENT_ALPHABET;
     }
 
-    public void setAutomaticTemporaryUpperCase() {
-        setShifted(true);
-        mShiftState.setAutomaticTemporaryUpperCase();
+    public static boolean isLetterCode(int code) {
+        return code >= MINIMUM_LETTER_CODE;
     }
 
-    public boolean isAutomaticTemporaryUpperCase() {
-        return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase();
-    }
+    public static class Params {
+        public KeyboardId mId;
+        public int mThemeId;
 
-    public boolean isManualTemporaryUpperCase() {
-        return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase();
-    }
+        /** Total height and width of the keyboard, including the paddings and keys */
+        public int mOccupiedHeight;
+        public int mOccupiedWidth;
 
-    public boolean isManualTemporaryUpperCaseFromAuto() {
-        return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto();
-    }
+        /** Base height and width of the keyboard used to calculate rows' or keys' heights and
+         *  widths
+         */
+        public int mBaseHeight;
+        public int mBaseWidth;
 
-    public KeyboardShiftState getKeyboardShiftState() {
-        return mShiftState;
-    }
+        public int mTopPadding;
+        public int mBottomPadding;
+        public int mHorizontalEdgesPadding;
+        public int mHorizontalCenterPadding;
 
-    public boolean isAlphaKeyboard() {
-        return mId.isAlphabetKeyboard();
-    }
+        public int mDefaultRowHeight;
+        public int mDefaultKeyWidth;
+        public int mHorizontalGap;
+        public int mVerticalGap;
 
-    public boolean isPhoneKeyboard() {
-        return mId.isPhoneKeyboard();
-    }
+        public int mMoreKeysTemplate;
+        public int mMaxMoreKeysKeyboardColumn;
 
-    public boolean isNumberKeyboard() {
-        return mId.isNumberKeyboard();
-    }
+        public int GRID_WIDTH;
+        public int GRID_HEIGHT;
 
-    public CharSequence adjustLabelCase(CharSequence label) {
-        if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3
-                && Character.isLowerCase(label.charAt(0))) {
-            return label.toString().toUpperCase(mId.mLocale);
+        public final HashSet<Key> mKeys = new HashSet<Key>();
+        public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
+        public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
+        public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+
+        public KeyboardSet.KeysCache mKeysCache;
+
+        public int mMostCommonKeyHeight = 0;
+        public int mMostCommonKeyWidth = 0;
+
+        public boolean mProximityCharsCorrectionEnabled;
+
+        public final TouchPositionCorrection mTouchPositionCorrection =
+                new TouchPositionCorrection();
+
+        public static class TouchPositionCorrection {
+            private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
+
+            public boolean mEnabled;
+            public float[] mXs;
+            public float[] mYs;
+            public float[] mRadii;
+
+            public void load(String[] data) {
+                final int dataLength = data.length;
+                if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
+                    if (LatinImeLogger.sDBG)
+                        throw new RuntimeException(
+                                "the size of touch position correction data is invalid");
+                    return;
+                }
+
+                final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                mXs = new float[length];
+                mYs = new float[length];
+                mRadii = new float[length];
+                try {
+                    for (int i = 0; i < dataLength; ++i) {
+                        final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                        final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                        final float value = Float.parseFloat(data[i]);
+                        if (type == 0) {
+                            mXs[index] = value;
+                        } else if (type == 1) {
+                            mYs[index] = value;
+                        } else {
+                            mRadii[index] = value;
+                        }
+                    }
+                } catch (NumberFormatException e) {
+                    if (LatinImeLogger.sDBG) {
+                        throw new RuntimeException(
+                                "the number format for touch position correction data is invalid");
+                    }
+                    mXs = null;
+                    mYs = null;
+                    mRadii = null;
+                }
+            }
+
+            // TODO: Remove this method.
+            public void setEnabled(boolean enabled) {
+                mEnabled = enabled;
+            }
+
+            public boolean isValid() {
+                return mEnabled && mXs != null && mYs != null && mRadii != null
+                    && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
+            }
         }
-        return label;
+
+        protected void clearKeys() {
+            mKeys.clear();
+            mShiftKeys.clear();
+            clearHistogram();
+        }
+
+        public void onAddKey(Key newKey) {
+            final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
+            mKeys.add(key);
+            updateHistogram(key);
+            if (key.mCode == Keyboard.CODE_SHIFT) {
+                mShiftKeys.add(key);
+            }
+            if (key.altCodeWhileTyping()) {
+                mAltCodeKeysWhileTyping.add(key);
+            }
+        }
+
+        private int mMaxHeightCount = 0;
+        private int mMaxWidthCount = 0;
+        private final HashMap<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>();
+        private final HashMap<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>();
+
+        private void clearHistogram() {
+            mMostCommonKeyHeight = 0;
+            mMaxHeightCount = 0;
+            mHeightHistogram.clear();
+
+            mMaxWidthCount = 0;
+            mMostCommonKeyWidth = 0;
+            mWidthHistogram.clear();
+        }
+
+        private static int updateHistogramCounter(HashMap<Integer, Integer> histogram,
+                Integer key) {
+            final int count = (histogram.containsKey(key) ? histogram.get(key) : 0) + 1;
+            histogram.put(key, count);
+            return count;
+        }
+
+        private void updateHistogram(Key key) {
+            final Integer height = key.mHeight + key.mVerticalGap;
+            final int heightCount = updateHistogramCounter(mHeightHistogram, height);
+            if (heightCount > mMaxHeightCount) {
+                mMaxHeightCount = heightCount;
+                mMostCommonKeyHeight = height;
+            }
+
+            final Integer width = key.mWidth + key.mHorizontalGap;
+            final int widthCount = updateHistogramCounter(mWidthHistogram, width);
+            if (widthCount > mMaxWidthCount) {
+                mMaxWidthCount = widthCount;
+                mMostCommonKeyWidth = width;
+            }
+        }
     }
 
     /**
-     * Returns the indices of the keys that are closest to the given point.
+     * Returns the array of the keys that are closest to the given point.
      * @param x the x-coordinate of the point
      * @param y the y-coordinate of the point
-     * @return the array of integer indices for the nearest keys to the given point. If the given
+     * @return the array of the nearest keys to the given point. If the given
      * point is out of range, then an array of size zero is returned.
      */
-    public int[] getNearestKeys(int x, int y) {
-        return mProximityInfo.getNearestKeys(x, y);
+    public Key[] getNearestKeys(int x, int y) {
+        // Avoid dead pixels at edges of the keyboard
+        final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
+        final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
+        return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
     }
 
-    public static String themeName(int themeId) {
-        // This should be aligned with theme-*.xml resource files' themeId attribute.
-        switch (themeId) {
-        case 0: return "Basic";
-        case 1: return "BasicHighContrast";
-        case 5: return "IceCreamSandwich";
-        case 6: return "Stone";
-        case 7: return "StoneBold";
-        case 8: return "GingerBread";
-        default: return null;
+    public static String printableCode(int code) {
+        switch (code) {
+        case CODE_SHIFT: return "shift";
+        case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
+        case CODE_OUTPUT_TEXT: return "text";
+        case CODE_DELETE: return "delete";
+        case CODE_SETTINGS: return "settings";
+        case CODE_SHORTCUT: return "shortcut";
+        case CODE_ACTION_ENTER: return "actionEnter";
+        case CODE_ACTION_NEXT: return "actionNext";
+        case CODE_ACTION_PREVIOUS: return "actionPrevious";
+        case CODE_LANGUAGE_SWITCH: return "languageSwitch";
+        case CODE_UNSPECIFIED: return "unspec";
+        case CODE_TAB: return "tab";
+        case CODE_ENTER: return "enter";
+        default:
+            if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code);
+            if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
+            if (code < 0x100) return String.format("'%c'", code);
+            return String.format("'\\u%04x'", code);
+        }
+    }
+
+   /**
+     * Keyboard Building helper.
+     *
+     * This class parses Keyboard XML file and eventually build a Keyboard.
+     * The Keyboard XML file looks like:
+     * <pre>
+     *   &gt;!-- xml/keyboard.xml --&lt;
+     *   &gt;Keyboard keyboard_attributes*&lt;
+     *     &gt;!-- Keyboard Content --&lt;
+     *     &gt;Row row_attributes*&lt;
+     *       &gt;!-- Row Content --&lt;
+     *       &gt;Key key_attributes* /&lt;
+     *       &gt;Spacer horizontalGap="32.0dp" /&lt;
+     *       &gt;include keyboardLayout="@xml/other_keys"&lt;
+     *       ...
+     *     &gt;/Row&lt;
+     *     &gt;include keyboardLayout="@xml/other_rows"&lt;
+     *     ...
+     *   &gt;/Keyboard&lt;
+     * </pre>
+     * The XML file which is included in other file must have &gt;merge&lt; as root element,
+     * such as:
+     * <pre>
+     *   &gt;!-- xml/other_keys.xml --&lt;
+     *   &gt;merge&lt;
+     *     &gt;Key key_attributes* /&lt;
+     *     ...
+     *   &gt;/merge&lt;
+     * </pre>
+     * and
+     * <pre>
+     *   &gt;!-- xml/other_rows.xml --&lt;
+     *   &gt;merge&lt;
+     *     &gt;Row row_attributes*&lt;
+     *       &gt;Key key_attributes* /&lt;
+     *     &gt;/Row&lt;
+     *     ...
+     *   &gt;/merge&lt;
+     * </pre>
+     * You can also use switch-case-default tags to select Rows and Keys.
+     * <pre>
+     *   &gt;switch&lt;
+     *     &gt;case case_attribute*&lt;
+     *       &gt;!-- Any valid tags at switch position --&lt;
+     *     &gt;/case&lt;
+     *     ...
+     *     &gt;default&lt;
+     *       &gt;!-- Any valid tags at switch position --&lt;
+     *     &gt;/default&lt;
+     *   &gt;/switch&lt;
+     * </pre>
+     * You can declare Key style and specify styles within Key tags.
+     * <pre>
+     *     &gt;switch&lt;
+     *       &gt;case mode="email"&lt;
+     *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
+     *           keyLabel=".com"
+     *         /&lt;
+     *       &gt;/case&lt;
+     *       &gt;case mode="url"&lt;
+     *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
+     *           keyLabel="http://"
+     *         /&lt;
+     *       &gt;/case&lt;
+     *     &gt;/switch&lt;
+     *     ...
+     *     &gt;Key keyStyle="shift-key" ... /&lt;
+     * </pre>
+     */
+
+    public static class Builder<KP extends Params> {
+        private static final String BUILDER_TAG = "Keyboard.Builder";
+        private static final boolean DEBUG = false;
+
+        // Keyboard XML Tags
+        private static final String TAG_KEYBOARD = "Keyboard";
+        private static final String TAG_ROW = "Row";
+        private static final String TAG_KEY = "Key";
+        private static final String TAG_SPACER = "Spacer";
+        private static final String TAG_INCLUDE = "include";
+        private static final String TAG_MERGE = "merge";
+        private static final String TAG_SWITCH = "switch";
+        private static final String TAG_CASE = "case";
+        private static final String TAG_DEFAULT = "default";
+        public static final String TAG_KEY_STYLE = "key-style";
+
+        private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
+        private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+        protected final KP mParams;
+        protected final Context mContext;
+        protected final Resources mResources;
+        private final DisplayMetrics mDisplayMetrics;
+
+        private int mCurrentY = 0;
+        private Row mCurrentRow = null;
+        private boolean mLeftEdge;
+        private boolean mTopEdge;
+        private Key mRightEdgeKey = null;
+        private final KeyStyles mKeyStyles = new KeyStyles();
+
+        /**
+         * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
+         * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
+         * defines.
+         */
+        public static class Row {
+            // keyWidth enum constants
+            private static final int KEYWIDTH_NOT_ENUM = 0;
+            private static final int KEYWIDTH_FILL_RIGHT = -1;
+            private static final int KEYWIDTH_FILL_BOTH = -2;
+
+            private final Params mParams;
+            /** Default width of a key in this row. */
+            private float mDefaultKeyWidth;
+            /** Default height of a key in this row. */
+            public final int mRowHeight;
+            /** Default keyLabelFlags in this row. */
+            private int mDefaultKeyLabelFlags;
+
+            private final int mCurrentY;
+            // Will be updated by {@link Key}'s constructor.
+            private float mCurrentX;
+
+            public Row(Resources res, Params params, XmlPullParser parser, int y) {
+                mParams = params;
+                TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+                        R.styleable.Keyboard);
+                mRowHeight = (int)Builder.getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_rowHeight,
+                        params.mBaseHeight, params.mDefaultRowHeight);
+                keyboardAttr.recycle();
+                TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+                        R.styleable.Keyboard_Key);
+                mDefaultKeyWidth = Builder.getDimensionOrFraction(keyAttr,
+                        R.styleable.Keyboard_Key_keyWidth,
+                        params.mBaseWidth, params.mDefaultKeyWidth);
+                keyAttr.recycle();
+
+                mDefaultKeyLabelFlags = 0;
+                mCurrentY = y;
+                mCurrentX = 0.0f;
+            }
+
+            public float getDefaultKeyWidth() {
+                return mDefaultKeyWidth;
+            }
+
+            public void setDefaultKeyWidth(float defaultKeyWidth) {
+                mDefaultKeyWidth = defaultKeyWidth;
+            }
+
+            public int getDefaultKeyLabelFlags() {
+                return mDefaultKeyLabelFlags;
+            }
+
+            public void setDefaultKeyLabelFlags(int keyLabelFlags) {
+                mDefaultKeyLabelFlags = keyLabelFlags;
+            }
+
+            public void setXPos(float keyXPos) {
+                mCurrentX = keyXPos;
+            }
+
+            public void advanceXPos(float width) {
+                mCurrentX += width;
+            }
+
+            public int getKeyY() {
+                return mCurrentY;
+            }
+
+            public float getKeyX(TypedArray keyAttr) {
+                final int widthType = Builder.getEnumValue(keyAttr,
+                        R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
+                if (widthType == KEYWIDTH_FILL_BOTH) {
+                    // If keyWidth is fillBoth, the key width should start right after the nearest
+                    // key on the left hand side.
+                    return mCurrentX;
+                }
+
+                final int keyboardRightEdge = mParams.mOccupiedWidth
+                        - mParams.mHorizontalEdgesPadding;
+                if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+                    final float keyXPos = Builder.getDimensionOrFraction(keyAttr,
+                            R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
+                    if (keyXPos < 0) {
+                        // If keyXPos is negative, the actual x-coordinate will be
+                        // keyboardWidth + keyXPos.
+                        // keyXPos shouldn't be less than mCurrentX because drawable area for this
+                        // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
+                        // its left hand side.
+                        return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
+                    } else {
+                        return keyXPos + mParams.mHorizontalEdgesPadding;
+                    }
+                }
+                return mCurrentX;
+            }
+
+            public float getKeyWidth(TypedArray keyAttr) {
+                return getKeyWidth(keyAttr, mCurrentX);
+            }
+
+            public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
+                final int widthType = Builder.getEnumValue(keyAttr,
+                        R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
+                switch (widthType) {
+                case KEYWIDTH_FILL_RIGHT:
+                case KEYWIDTH_FILL_BOTH:
+                    final int keyboardRightEdge =
+                            mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
+                    // If keyWidth is fillRight, the actual key width will be determined to fill
+                    // out the area up to the right edge of the keyboard.
+                    // If keyWidth is fillBoth, the actual key width will be determined to fill out
+                    // the area between the nearest key on the left hand side and the right edge of
+                    // the keyboard.
+                    return keyboardRightEdge - keyXPos;
+                default: // KEYWIDTH_NOT_ENUM
+                    return Builder.getDimensionOrFraction(keyAttr,
+                            R.styleable.Keyboard_Key_keyWidth,
+                            mParams.mBaseWidth, mDefaultKeyWidth);
+                }
+            }
+        }
+
+        public Builder(Context context, KP params) {
+            mContext = context;
+            final Resources res = context.getResources();
+            mResources = res;
+            mDisplayMetrics = res.getDisplayMetrics();
+
+            mParams = params;
+
+            params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
+            params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
+        }
+
+        public void setAutoGenerate(KeyboardSet.KeysCache keysCache) {
+            mParams.mKeysCache = keysCache;
+        }
+
+        public Builder<KP> load(int xmlId, KeyboardId id) {
+            mParams.mId = id;
+            final XmlResourceParser parser = mResources.getXml(xmlId);
+            try {
+                parseKeyboard(parser);
+            } catch (XmlPullParserException e) {
+                Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+                throw new IllegalArgumentException(e);
+            } catch (IOException e) {
+                Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+                throw new RuntimeException(e);
+            } finally {
+                parser.close();
+            }
+            return this;
+        }
+
+        // TODO: Remove this method.
+        public void setTouchPositionCorrectionEnabled(boolean enabled) {
+            mParams.mTouchPositionCorrection.setEnabled(enabled);
+        }
+
+        public void setProximityCharsCorrectionEnabled(boolean enabled) {
+            mParams.mProximityCharsCorrectionEnabled = enabled;
+        }
+
+        public Keyboard build() {
+            return new Keyboard(mParams);
+        }
+
+        private int mIndent;
+        private static final String SPACES = "                                             ";
+
+        private static String spaces(int count) {
+            return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
+        }
+
+        private void startTag(String format, Object ... args) {
+            Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+        }
+
+        private void endTag(String format, Object ... args) {
+            Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
+        }
+
+        private void startEndTag(String format, Object ... args) {
+            Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+            mIndent--;
+        }
+
+        private void parseKeyboard(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
+            int event;
+            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (event == XmlPullParser.START_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_KEYBOARD.equals(tag)) {
+                        parseKeyboardAttributes(parser);
+                        startKeyboard();
+                        parseKeyboardContent(parser, false);
+                        break;
+                    } else {
+                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
+                    }
+                }
+            }
+        }
+
+        private void parseKeyboardAttributes(XmlPullParser parser) {
+            final int displayWidth = mDisplayMetrics.widthPixels;
+            final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
+                    Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
+                    R.style.Keyboard);
+            final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.Keyboard_Key);
+            try {
+                final int displayHeight = mDisplayMetrics.heightPixels;
+                final String keyboardHeightString = Utils.getDeviceOverrideValue(
+                        mResources, R.array.keyboard_heights, null);
+                final float keyboardHeight;
+                if (keyboardHeightString != null) {
+                    keyboardHeight = Float.parseFloat(keyboardHeightString)
+                            * mDisplayMetrics.density;
+                } else {
+                    keyboardHeight = keyboardAttr.getDimension(
+                            R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+                }
+                final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
+                float minKeyboardHeight = getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
+                if (minKeyboardHeight < 0) {
+                    // Specified fraction was negative, so it should be calculated against display
+                    // width.
+                    minKeyboardHeight = -getDimensionOrFraction(keyboardAttr,
+                            R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
+                }
+                final Params params = mParams;
+                // Keyboard height will not exceed maxKeyboardHeight and will not be less than
+                // minKeyboardHeight.
+                params.mOccupiedHeight = (int)Math.max(
+                        Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
+                params.mOccupiedWidth = params.mId.mWidth;
+                params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
+                params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
+                params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
+                        mParams.mOccupiedWidth, 0);
+
+                params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
+                        - params.mHorizontalCenterPadding;
+                params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr,
+                        R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
+                        params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
+                params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
+                params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
+                params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
+                        - params.mBottomPadding + params.mVerticalGap;
+                params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_rowHeight, params.mBaseHeight,
+                        params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
+
+                params.mMoreKeysTemplate = keyboardAttr.getResourceId(
+                        R.styleable.Keyboard_moreKeysTemplate, 0);
+                params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
+                        R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
+
+                params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
+                params.mIconsSet.loadIcons(keyboardAttr);
+
+                final int resourceId = keyboardAttr.getResourceId(
+                        R.styleable.Keyboard_touchPositionCorrectionData, 0);
+                params.mTouchPositionCorrection.setEnabled(resourceId != 0);
+                if (resourceId != 0) {
+                    final String[] data = mResources.getStringArray(resourceId);
+                    params.mTouchPositionCorrection.load(data);
+                }
+            } finally {
+                keyAttr.recycle();
+                keyboardAttr.recycle();
+            }
+        }
+
+        private void parseKeyboardContent(XmlPullParser parser, boolean skip)
+                throws XmlPullParserException, IOException {
+            int event;
+            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (event == XmlPullParser.START_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_ROW.equals(tag)) {
+                        Row row = parseRowAttributes(parser);
+                        if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
+                        if (!skip) {
+                            startRow(row);
+                        }
+                        parseRowContent(parser, row, skip);
+                    } else if (TAG_INCLUDE.equals(tag)) {
+                        parseIncludeKeyboardContent(parser, skip);
+                    } else if (TAG_SWITCH.equals(tag)) {
+                        parseSwitchKeyboardContent(parser, skip);
+                    } else if (TAG_KEY_STYLE.equals(tag)) {
+                        parseKeyStyle(parser, skip);
+                    } else {
+                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
+                    }
+                } else if (event == XmlPullParser.END_TAG) {
+                    final String tag = parser.getName();
+                    if (DEBUG) endTag("</%s>", tag);
+                    if (TAG_KEYBOARD.equals(tag)) {
+                        endKeyboard();
+                        break;
+                    } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+                            || TAG_MERGE.equals(tag)) {
+                        break;
+                    } else {
+                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
+                    }
+                }
+            }
+        }
+
+        private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException {
+            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.Keyboard);
+            try {
+                if (a.hasValue(R.styleable.Keyboard_horizontalGap))
+                    throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
+                if (a.hasValue(R.styleable.Keyboard_verticalGap))
+                    throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
+                return new Row(mResources, mParams, parser, mCurrentY);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        private void parseRowContent(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            int event;
+            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (event == XmlPullParser.START_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_KEY.equals(tag)) {
+                        parseKey(parser, row, skip);
+                    } else if (TAG_SPACER.equals(tag)) {
+                        parseSpacer(parser, row, skip);
+                    } else if (TAG_INCLUDE.equals(tag)) {
+                        parseIncludeRowContent(parser, row, skip);
+                    } else if (TAG_SWITCH.equals(tag)) {
+                        parseSwitchRowContent(parser, row, skip);
+                    } else if (TAG_KEY_STYLE.equals(tag)) {
+                        parseKeyStyle(parser, skip);
+                    } else {
+                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+                    }
+                } else if (event == XmlPullParser.END_TAG) {
+                    final String tag = parser.getName();
+                    if (DEBUG) endTag("</%s>", tag);
+                    if (TAG_ROW.equals(tag)) {
+                        if (!skip) {
+                            endRow(row);
+                        }
+                        break;
+                    } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+                            || TAG_MERGE.equals(tag)) {
+                        break;
+                    } else {
+                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+                    }
+                }
+            }
+        }
+
+        private void parseKey(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (skip) {
+                XmlParseUtils.checkEndTag(TAG_KEY, parser);
+                if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
+            } else {
+                final Key key = new Key(mResources, mParams, row, parser, mKeyStyles);
+                if (DEBUG) {
+                    startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
+                            (key.isEnabled() ? "" : " disabled"), key,
+                            Arrays.toString(key.mMoreKeys));
+                }
+                XmlParseUtils.checkEndTag(TAG_KEY, parser);
+                endKey(key);
+            }
+        }
+
+        private void parseSpacer(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (skip) {
+                XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+                if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
+            } else {
+                final Key.Spacer spacer = new Key.Spacer(
+                        mResources, mParams, row, parser, mKeyStyles);
+                if (DEBUG) startEndTag("<%s />", TAG_SPACER);
+                XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+                endKey(spacer);
+            }
+        }
+
+        private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip)
+                throws XmlPullParserException, IOException {
+            parseIncludeInternal(parser, null, skip);
+        }
+
+        private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            parseIncludeInternal(parser, row, skip);
+        }
+
+        private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (skip) {
+                XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+                if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
+            } else {
+                final AttributeSet attr = Xml.asAttributeSet(parser);
+                final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
+                        R.styleable.Keyboard_Include);
+                final TypedArray keyAttr = mResources.obtainAttributes(attr,
+                        R.styleable.Keyboard_Key);
+                int keyboardLayout = 0;
+                float savedDefaultKeyWidth = 0;
+                int savedDefaultKeyLabelFlags = 0;
+                try {
+                    XmlParseUtils.checkAttributeExists(keyboardAttr,
+                            R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
+                            TAG_INCLUDE, parser);
+                    keyboardLayout = keyboardAttr.getResourceId(
+                            R.styleable.Keyboard_Include_keyboardLayout, 0);
+                    if (row != null) {
+                        savedDefaultKeyWidth = row.getDefaultKeyWidth();
+                        savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
+                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+                            // Override current x coordinate.
+                            row.setXPos(row.getKeyX(keyAttr));
+                        }
+                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
+                            // Override default key width.
+                            row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
+                        }
+                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyLabelFlags)) {
+                            // Override default key label flags.
+                            row.setDefaultKeyLabelFlags(
+                                    keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0)
+                                    | savedDefaultKeyLabelFlags);
+                        }
+                    }
+                } finally {
+                    keyboardAttr.recycle();
+                    keyAttr.recycle();
+                }
+
+                XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+                if (DEBUG) {
+                    startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
+                            mResources.getResourceEntryName(keyboardLayout));
+                }
+                final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
+                try {
+                    parseMerge(parserForInclude, row, skip);
+                } finally {
+                    if (row != null) {
+                        // Restore default key width and key label flags.
+                        row.setDefaultKeyWidth(savedDefaultKeyWidth);
+                        row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
+                    }
+                    parserForInclude.close();
+                }
+            }
+        }
+
+        private void parseMerge(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (DEBUG) startTag("<%s>", TAG_MERGE);
+            int event;
+            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (event == XmlPullParser.START_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_MERGE.equals(tag)) {
+                        if (row == null) {
+                            parseKeyboardContent(parser, skip);
+                        } else {
+                            parseRowContent(parser, row, skip);
+                        }
+                        break;
+                    } else {
+                        throw new XmlParseUtils.ParseException(
+                                "Included keyboard layout must have <merge> root element", parser);
+                    }
+                }
+            }
+        }
+
+        private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip)
+                throws XmlPullParserException, IOException {
+            parseSwitchInternal(parser, null, skip);
+        }
+
+        private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            parseSwitchInternal(parser, row, skip);
+        }
+
+        private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
+            boolean selected = false;
+            int event;
+            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (event == XmlPullParser.START_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_CASE.equals(tag)) {
+                        selected |= parseCase(parser, row, selected ? true : skip);
+                    } else if (TAG_DEFAULT.equals(tag)) {
+                        selected |= parseDefault(parser, row, selected ? true : skip);
+                    } else {
+                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+                    }
+                } else if (event == XmlPullParser.END_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_SWITCH.equals(tag)) {
+                        if (DEBUG) endTag("</%s>", TAG_SWITCH);
+                        break;
+                    } else {
+                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+                    }
+                }
+            }
+        }
+
+        private boolean parseCase(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            final boolean selected = parseCaseCondition(parser);
+            if (row == null) {
+                // Processing Rows.
+                parseKeyboardContent(parser, selected ? skip : true);
+            } else {
+                // Processing Keys.
+                parseRowContent(parser, row, selected ? skip : true);
+            }
+            return selected;
+        }
+
+        private boolean parseCaseCondition(XmlPullParser parser) {
+            final KeyboardId id = mParams.mId;
+            if (id == null)
+                return true;
+
+            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.Keyboard_Case);
+            try {
+                final boolean keyboardSetElementMatched = matchTypedValue(a,
+                        R.styleable.Keyboard_Case_keyboardSetElement, id.mElementId,
+                        KeyboardId.elementIdToName(id.mElementId));
+                final boolean modeMatched = matchTypedValue(a,
+                        R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
+                final boolean navigateNextMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
+                final boolean navigatePreviousMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
+                final boolean passwordInputMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
+                final boolean clobberSettingsKeyMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
+                final boolean shortcutKeyEnabledMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
+                final boolean hasShortcutKeyMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
+                final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                        id.mLanguageSwitchKeyEnabled);
+                final boolean isMultiLineMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
+                final boolean imeActionMatched = matchInteger(a,
+                        R.styleable.Keyboard_Case_imeAction, id.imeAction());
+                final boolean localeCodeMatched = matchString(a,
+                        R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
+                final boolean languageCodeMatched = matchString(a,
+                        R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
+                final boolean countryCodeMatched = matchString(a,
+                        R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+                final boolean selected = keyboardSetElementMatched && modeMatched
+                        && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
+                        && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
+                        && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
+                        && isMultiLineMatched && imeActionMatched && localeCodeMatched
+                        && languageCodeMatched && countryCodeMatched;
+
+                if (DEBUG) {
+                    startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+                            textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement),
+                                    "keyboardSetElement"),
+                            textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
+                            textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
+                                    "imeAction"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
+                                    "navigateNext"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
+                                    "navigatePrevious"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
+                                    "clobberSettingsKey"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
+                                    "passwordInput"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
+                                    "shortcutKeyEnabled"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
+                                    "hasShortcutKey"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                                    "languageSwitchKeyEnabled"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
+                                    "isMultiLine"),
+                            textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
+                                    "localeCode"),
+                            textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
+                                    "languageCode"),
+                            textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
+                                    "countryCode"),
+                            selected ? "" : " skipped");
+                }
+
+                return selected;
+            } finally {
+                a.recycle();
+            }
+        }
+
+        private static boolean matchInteger(TypedArray a, int index, int value) {
+            // If <case> does not have "index" attribute, that means this <case> is wild-card for
+            // the attribute.
+            return !a.hasValue(index) || a.getInt(index, 0) == value;
+        }
+
+        private static boolean matchBoolean(TypedArray a, int index, boolean value) {
+            // If <case> does not have "index" attribute, that means this <case> is wild-card for
+            // the attribute.
+            return !a.hasValue(index) || a.getBoolean(index, false) == value;
+        }
+
+        private static boolean matchString(TypedArray a, int index, String value) {
+            // If <case> does not have "index" attribute, that means this <case> is wild-card for
+            // the attribute.
+            return !a.hasValue(index)
+                    || stringArrayContains(a.getString(index).split("\\|"), value);
+        }
+
+        private static boolean matchTypedValue(TypedArray a, int index, int intValue,
+                String strValue) {
+            // If <case> does not have "index" attribute, that means this <case> is wild-card for
+            // the attribute.
+            final TypedValue v = a.peekValue(index);
+            if (v == null)
+                return true;
+
+            if (isIntegerValue(v)) {
+                return intValue == a.getInt(index, 0);
+            } else if (isStringValue(v)) {
+                return stringArrayContains(a.getString(index).split("\\|"), strValue);
+            }
+            return false;
+        }
+
+        private static boolean stringArrayContains(String[] array, String value) {
+            for (final String elem : array) {
+                if (elem.equals(value))
+                    return true;
+            }
+            return false;
+        }
+
+        private boolean parseDefault(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (DEBUG) startTag("<%s>", TAG_DEFAULT);
+            if (row == null) {
+                parseKeyboardContent(parser, skip);
+            } else {
+                parseRowContent(parser, row, skip);
+            }
+            return true;
+        }
+
+        private void parseKeyStyle(XmlPullParser parser, boolean skip)
+                throws XmlPullParserException, IOException {
+            TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.Keyboard_KeyStyle);
+            TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.Keyboard_Key);
+            try {
+                if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
+                    throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+                            + "/> needs styleName attribute", parser);
+                if (DEBUG) {
+                    startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
+                        keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
+                        skip ? " skipped" : "");
+                }
+                if (!skip)
+                    mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+            } finally {
+                keyStyleAttr.recycle();
+                keyAttrs.recycle();
+            }
+            XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
+        }
+
+        private void startKeyboard() {
+            mCurrentY += mParams.mTopPadding;
+            mTopEdge = true;
+        }
+
+        private void startRow(Row row) {
+            addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+            mCurrentRow = row;
+            mLeftEdge = true;
+            mRightEdgeKey = null;
+        }
+
+        private void endRow(Row row) {
+            if (mCurrentRow == null)
+                throw new InflateException("orphant end row tag");
+            if (mRightEdgeKey != null) {
+                mRightEdgeKey.markAsRightEdge(mParams);
+                mRightEdgeKey = null;
+            }
+            addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+            mCurrentY += row.mRowHeight;
+            mCurrentRow = null;
+            mTopEdge = false;
+        }
+
+        private void endKey(Key key) {
+            mParams.onAddKey(key);
+            if (mLeftEdge) {
+                key.markAsLeftEdge(mParams);
+                mLeftEdge = false;
+            }
+            if (mTopEdge) {
+                key.markAsTopEdge(mParams);
+            }
+            mRightEdgeKey = key;
+        }
+
+        private void endKeyboard() {
+            // nothing to do here.
+        }
+
+        private void addEdgeSpace(float width, Row row) {
+            row.advanceXPos(width);
+            mLeftEdge = false;
+            mRightEdgeKey = null;
+        }
+
+        public static float getDimensionOrFraction(TypedArray a, int index, int base,
+                float defValue) {
+            final TypedValue value = a.peekValue(index);
+            if (value == null)
+                return defValue;
+            if (isFractionValue(value)) {
+                return a.getFraction(index, base, base, defValue);
+            } else if (isDimensionValue(value)) {
+                return a.getDimension(index, defValue);
+            }
+            return defValue;
+        }
+
+        public static int getEnumValue(TypedArray a, int index, int defValue) {
+            final TypedValue value = a.peekValue(index);
+            if (value == null)
+                return defValue;
+            if (isIntegerValue(value)) {
+                return a.getInt(index, defValue);
+            }
+            return defValue;
+        }
+
+        private static boolean isFractionValue(TypedValue v) {
+            return v.type == TypedValue.TYPE_FRACTION;
+        }
+
+        private static boolean isDimensionValue(TypedValue v) {
+            return v.type == TypedValue.TYPE_DIMENSION;
+        }
+
+        private static boolean isIntegerValue(TypedValue v) {
+            return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
+        }
+
+        private static boolean isStringValue(TypedValue v) {
+            return v.type == TypedValue.TYPE_STRING;
+        }
+
+        private static String textAttr(String value, String name) {
+            return value != null ? String.format(" %s=%s", name, value) : "";
+        }
+
+        private static String booleanAttr(TypedArray a, int index, String name) {
+            return a.hasValue(index)
+                    ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 6f54208..275aacf 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -24,10 +24,8 @@
      *
      * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
      *            the value will be zero.
-     * @param withSliding true if pressing has occurred because the user slid finger from other key
-     *             to this key without releasing the finger.
      */
-    public void onPress(int primaryCode, boolean withSliding);
+    public void onPressKey(int primaryCode);
 
     /**
      * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
@@ -37,27 +35,26 @@
      * @param withSliding true if releasing has occurred because the user slid finger from the key
      *             to other key without releasing the finger.
      */
-    public void onRelease(int primaryCode, boolean withSliding);
+    public void onReleaseKey(int primaryCode, boolean withSliding);
 
     /**
      * Send a key code to the listener.
      *
      * @param primaryCode this is the code of 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.
      * @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
-     *            {@link PointerTracker#onTouchEvent} or so, the value should be
-     *            {@link #NOT_A_TOUCH_COORDINATE}.
+     *            {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
+     *            If it's called on insertion from the suggestion strip, it should be
+     *            {@link #SUGGESTION_STRIP_COORDINATE}.
      * @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
-     *            {@link PointerTracker#onTouchEvent} or so, the value should be
-     *            {@link #NOT_A_TOUCH_COORDINATE}.
+     *            {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
+     *            If it's called on insertion from the suggestion strip, it should be
+     *            {@link #SUGGESTION_STRIP_COORDINATE}.
      */
-    public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y);
+    public void onCodeInput(int primaryCode, int x, int y);
 
     public static final int NOT_A_TOUCH_COORDINATE = -1;
+    public static final int SUGGESTION_STRIP_COORDINATE = -2;
+    public static final int SPELL_CHECKER_COORDINATE = -3;
 
     /**
      * Sends a sequence of characters to the listener.
@@ -79,11 +76,11 @@
 
     public static class Adapter implements KeyboardActionListener {
         @Override
-        public void onPress(int primaryCode, boolean withSliding) {}
+        public void onPressKey(int primaryCode) {}
         @Override
-        public void onRelease(int primaryCode, boolean withSliding) {}
+        public void onReleaseKey(int primaryCode, boolean withSliding) {}
         @Override
-        public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {}
+        public void onCodeInput(int primaryCode, int x, int y) {}
         @Override
         public void onTextInput(CharSequence text) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 2e4988f..e350818 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -16,18 +16,18 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.text.InputType;
+import android.text.TextUtils;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.InputTypeUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
 
 /**
- * Represents the parameters necessary to construct a new LatinKeyboard,
- * which also serve as a unique identifier for each keyboard type.
+ * Unique identifier for each keyboard type.
  */
 public class KeyboardId {
     public static final int MODE_TEXT = 0;
@@ -36,105 +36,132 @@
     public static final int MODE_IM = 3;
     public static final int MODE_PHONE = 4;
     public static final int MODE_NUMBER = 5;
+    public static final int MODE_DATE = 6;
+    public static final int MODE_TIME = 7;
+    public static final int MODE_DATETIME = 8;
 
-    public static final int F2KEY_MODE_NONE = 0;
-    public static final int F2KEY_MODE_SETTINGS = 1;
-    public static final int F2KEY_MODE_SHORTCUT_IME = 2;
-    public static final int F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS = 3;
+    public static final int ELEMENT_ALPHABET = 0;
+    public static final int ELEMENT_ALPHABET_MANUAL_SHIFTED = 1;
+    public static final int ELEMENT_ALPHABET_AUTOMATIC_SHIFTED = 2;
+    public static final int ELEMENT_ALPHABET_SHIFT_LOCKED = 3;
+    public static final int ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED = 4;
+    public static final int ELEMENT_SYMBOLS = 5;
+    public static final int ELEMENT_SYMBOLS_SHIFTED = 6;
+    public static final int ELEMENT_PHONE = 7;
+    public static final int ELEMENT_PHONE_SYMBOLS = 8;
+    public static final int ELEMENT_NUMBER = 9;
+
+    private static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
 
     public final Locale mLocale;
     public final int mOrientation;
     public final int mWidth;
     public final int mMode;
-    public final int mXmlId;
-    public final boolean mNavigateAction;
-    public final boolean mPasswordInput;
-    // TODO: Clean up these booleans and modes.
-    public final boolean mHasSettingsKey;
-    public final int mF2KeyMode;
+    public final int mElementId;
+    private final EditorInfo mEditorInfo;
     public final boolean mClobberSettingsKey;
     public final boolean mShortcutKeyEnabled;
     public final boolean mHasShortcutKey;
-    public final int mImeAction;
-
-    public final String mXmlName;
-    public final EditorInfo mAttribute;
+    public final boolean mLanguageSwitchKeyEnabled;
+    public final String mCustomActionLabel;
 
     private final int mHashCode;
 
-    public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int width,
-            int mode, EditorInfo attribute, boolean hasSettingsKey, int f2KeyMode,
-            boolean clobberSettingsKey, boolean shortcutKeyEnabled, boolean hasShortcutKey) {
-        final int inputType = (attribute != null) ? attribute.inputType : 0;
-        final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
-        this.mLocale = locale;
-        this.mOrientation = orientation;
-        this.mWidth = width;
-        this.mMode = mode;
-        this.mXmlId = xmlId;
-        // Note: Turn off checking navigation flag to show TAB key for now.
-        this.mNavigateAction = InputTypeCompatUtils.isWebInputType(inputType);
-//                || EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
-//                || EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions);
-        this.mPasswordInput = InputTypeCompatUtils.isPasswordInputType(inputType)
-                || InputTypeCompatUtils.isVisiblePasswordInputType(inputType);
-        this.mHasSettingsKey = hasSettingsKey;
-        this.mF2KeyMode = f2KeyMode;
-        this.mClobberSettingsKey = clobberSettingsKey;
-        this.mShortcutKeyEnabled = shortcutKeyEnabled;
-        this.mHasShortcutKey = hasShortcutKey;
-        // We are interested only in {@link EditorInfo#IME_MASK_ACTION} enum value and
-        // {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION}.
-        this.mImeAction = imeOptions & (
-                EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
+    public KeyboardId(int elementId, Locale locale, int orientation, int width, int mode,
+            EditorInfo editorInfo, boolean clobberSettingsKey, boolean shortcutKeyEnabled,
+            boolean hasShortcutKey, boolean languageSwitchKeyEnabled) {
+        mLocale = locale;
+        mOrientation = orientation;
+        mWidth = width;
+        mMode = mode;
+        mElementId = elementId;
+        mEditorInfo = editorInfo;
+        mClobberSettingsKey = clobberSettingsKey;
+        mShortcutKeyEnabled = shortcutKeyEnabled;
+        mHasShortcutKey = hasShortcutKey;
+        mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
+        mCustomActionLabel = (editorInfo.actionLabel != null)
+                ? editorInfo.actionLabel.toString() : null;
 
-        this.mXmlName = xmlName;
-        this.mAttribute = attribute;
+        mHashCode = computeHashCode(this);
+    }
 
-        this.mHashCode = Arrays.hashCode(new Object[] {
-                locale,
-                orientation,
-                width,
-                mode,
-                xmlId,
-                mNavigateAction,
-                mPasswordInput,
-                hasSettingsKey,
-                f2KeyMode,
-                clobberSettingsKey,
-                shortcutKeyEnabled,
-                hasShortcutKey,
-                mImeAction,
+    private static int computeHashCode(KeyboardId id) {
+        return Arrays.hashCode(new Object[] {
+                id.mOrientation,
+                id.mElementId,
+                id.mMode,
+                id.mWidth,
+                id.passwordInput(),
+                id.mClobberSettingsKey,
+                id.mShortcutKeyEnabled,
+                id.mHasShortcutKey,
+                id.mLanguageSwitchKeyEnabled,
+                id.isMultiLine(),
+                id.imeAction(),
+                id.mCustomActionLabel,
+                id.navigateNext(),
+                id.navigatePrevious(),
+                id.mLocale
         });
     }
 
-    public KeyboardId cloneWithNewXml(String xmlName, int xmlId) {
-        return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mAttribute,
-                false, F2KEY_MODE_NONE, false, false, false);
-    }
-
-    public int getXmlId() {
-        return mXmlId;
+    private boolean equals(KeyboardId other) {
+        if (other == this)
+            return true;
+        return other.mOrientation == mOrientation
+                && other.mElementId == mElementId
+                && other.mMode == mMode
+                && other.mWidth == mWidth
+                && other.passwordInput() == passwordInput()
+                && other.mClobberSettingsKey == mClobberSettingsKey
+                && other.mShortcutKeyEnabled == mShortcutKeyEnabled
+                && other.mHasShortcutKey == mHasShortcutKey
+                && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
+                && other.isMultiLine() == isMultiLine()
+                && other.imeAction() == imeAction()
+                && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
+                && other.navigateNext() == navigateNext()
+                && other.navigatePrevious() == navigatePrevious()
+                && other.mLocale.equals(mLocale);
     }
 
     public boolean isAlphabetKeyboard() {
-        return mXmlId == R.xml.kbd_qwerty;
+        return mElementId < ELEMENT_SYMBOLS;
     }
 
-    public boolean isSymbolsKeyboard() {
-        return mXmlId == R.xml.kbd_symbols || mXmlId == R.xml.kbd_symbols_shift;
+    public boolean navigateNext() {
+        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0;
     }
 
-    public boolean isPhoneKeyboard() {
-        return mMode == MODE_PHONE;
+    public boolean navigatePrevious() {
+        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0;
     }
 
-    public boolean isPhoneShiftKeyboard() {
-        return mXmlId == R.xml.kbd_phone_shift;
+    public boolean passwordInput() {
+        final int inputType = mEditorInfo.inputType;
+        return InputTypeUtils.isPasswordInputType(inputType)
+                || InputTypeUtils.isVisiblePasswordInputType(inputType);
     }
 
-    public boolean isNumberKeyboard() {
-        return mMode == MODE_NUMBER;
+    public boolean isMultiLine() {
+        return (mEditorInfo.inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
+    }
+
+    public int imeAction() {
+        final int actionId = mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
+        if ((mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+            return EditorInfo.IME_ACTION_NONE;
+        } else if (mEditorInfo.actionLabel != null) {
+            return IME_ACTION_CUSTOM_LABEL;
+        } else {
+            return actionId;
+        }
+    }
+
+    public int imeActionId() {
+        final int actionId = imeAction();
+        return actionId == IME_ACTION_CUSTOM_LABEL ? mEditorInfo.actionId : actionId;
     }
 
     @Override
@@ -142,22 +169,6 @@
         return other instanceof KeyboardId && equals((KeyboardId) other);
     }
 
-    private boolean equals(KeyboardId other) {
-        return other.mLocale.equals(this.mLocale)
-            && other.mOrientation == this.mOrientation
-            && other.mWidth == this.mWidth
-            && other.mMode == this.mMode
-            && other.mXmlId == this.mXmlId
-            && other.mNavigateAction == this.mNavigateAction
-            && other.mPasswordInput == this.mPasswordInput
-            && other.mHasSettingsKey == this.mHasSettingsKey
-            && other.mF2KeyMode == this.mF2KeyMode
-            && other.mClobberSettingsKey == this.mClobberSettingsKey
-            && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled
-            && other.mHasShortcutKey == this.mHasShortcutKey
-            && other.mImeAction == this.mImeAction;
-    }
-
     @Override
     public int hashCode() {
         return mHashCode;
@@ -165,22 +176,47 @@
 
     @Override
     public String toString() {
-        return String.format("[%s.xml %s %s%d %s %s %s%s%s%s%s%s%s]",
-                mXmlName,
+        return String.format("[%s %s %s%d %s %s %s%s%s%s%s%s%s%s]",
+                elementIdToName(mElementId),
                 mLocale,
                 (mOrientation == 1 ? "port" : "land"), mWidth,
                 modeName(mMode),
-                EditorInfoCompatUtils.imeOptionsName(mImeAction),
-                f2KeyModeName(mF2KeyMode),
+                imeAction(),
+                (navigateNext() ? "navigateNext" : ""),
+                (navigatePrevious() ? "navigatePrevious" : ""),
                 (mClobberSettingsKey ? " clobberSettingsKey" : ""),
-                (mNavigateAction ? " navigateAction" : ""),
-                (mPasswordInput ? " passwordInput" : ""),
-                (mHasSettingsKey ? " hasSettingsKey" : ""),
+                (passwordInput() ? " passwordInput" : ""),
                 (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
-                (mHasShortcutKey ? " hasShortcutKey" : "")
+                (mHasShortcutKey ? " hasShortcutKey" : ""),
+                (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
+                (isMultiLine() ? "isMultiLine" : "")
         );
     }
 
+    public static boolean equivalentEditorInfoForKeyboard(EditorInfo a, EditorInfo b) {
+        if (a == null && b == null) return true;
+        if (a == null || b == null) return false;
+        return a.inputType == b.inputType
+                && a.imeOptions == b.imeOptions
+                && TextUtils.equals(a.privateImeOptions, b.privateImeOptions);
+    }
+
+    public static String elementIdToName(int elementId) {
+        switch (elementId) {
+        case ELEMENT_ALPHABET: return "alphabet";
+        case ELEMENT_ALPHABET_MANUAL_SHIFTED: return "alphabetManualShifted";
+        case ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: return "alphabetAutomaticShifted";
+        case ELEMENT_ALPHABET_SHIFT_LOCKED: return "alphabetShiftLocked";
+        case ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: return "alphabetShiftLockShifted";
+        case ELEMENT_SYMBOLS: return "symbols";
+        case ELEMENT_SYMBOLS_SHIFTED: return "symbolsShifted";
+        case ELEMENT_PHONE: return "phone";
+        case ELEMENT_PHONE_SYMBOLS: return "phoneSymbols";
+        case ELEMENT_NUMBER: return "number";
+        default: return null;
+        }
+    }
+
     public static String modeName(int mode) {
         switch (mode) {
         case MODE_TEXT: return "text";
@@ -189,17 +225,15 @@
         case MODE_IM: return "im";
         case MODE_PHONE: return "phone";
         case MODE_NUMBER: return "number";
+        case MODE_DATE: return "date";
+        case MODE_TIME: return "time";
+        case MODE_DATETIME: return "datetime";
         default: return null;
         }
     }
 
-    public static String f2KeyModeName(int f2KeyMode) {
-        switch (f2KeyMode) {
-        case F2KEY_MODE_NONE: return "none";
-        case F2KEY_MODE_SETTINGS: return "settings";
-        case F2KEY_MODE_SHORTCUT_IME: return "shortcutIme";
-        case F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS: return "shortcutImeOrSettings";
-        default: return null;
-        }
+    public static String actionName(int actionId) {
+        return (actionId == IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel"
+                : EditorInfoCompatUtils.imeActionName(actionId);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
new file mode 100644
index 0000000..263f17f
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.text.InputType;
+import android.util.Log;
+import android.util.Xml;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.keyboard.KeyboardSet.Params.ElementParams;
+import com.android.inputmethod.latin.InputTypeUtils;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.XmlParseUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * This class represents a set of keyboards. Each of them represents a different keyboard
+ * specific to a keyboard state, such as alphabet, symbols, and so on.  Layouts in the same
+ * {@link KeyboardSet} are related to each other.
+ * A {@link KeyboardSet} needs to be created for each {@link android.view.inputmethod.EditorInfo}.
+ */
+public class KeyboardSet {
+    private static final String TAG = KeyboardSet.class.getSimpleName();
+    private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
+
+    private static final String TAG_KEYBOARD_SET = "KeyboardSet";
+    private static final String TAG_ELEMENT = "Element";
+
+    private final Context mContext;
+    private final Params mParams;
+
+    private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
+            new HashMap<KeyboardId, SoftReference<Keyboard>>();
+    private static final KeysCache sKeysCache = new KeysCache();
+
+    public static class KeyboardSetException extends RuntimeException {
+        public final KeyboardId mKeyboardId;
+
+        public KeyboardSetException(Throwable cause, KeyboardId keyboardId) {
+            super(cause);
+            mKeyboardId = keyboardId;
+        }
+    }
+
+    public static class KeysCache {
+        private final HashMap<Key, Key> mMap;
+
+        public KeysCache() {
+            mMap = new HashMap<Key, Key>();
+        }
+
+        public void clear() {
+            mMap.clear();
+        }
+
+        public Key get(Key key) {
+            final Key existingKey = mMap.get(key);
+            if (existingKey != null) {
+                // Reuse the existing element that equals to "key" without adding "key" to the map.
+                return existingKey;
+            }
+            mMap.put(key, key);
+            return key;
+        }
+    }
+
+    static class Params {
+        int mMode;
+        EditorInfo mEditorInfo;
+        boolean mTouchPositionCorrectionEnabled;
+        boolean mDisableShortcutKey;
+        boolean mVoiceKeyEnabled;
+        boolean mVoiceKeyOnMain;
+        boolean mNoSettingsKey;
+        boolean mLanguageSwitchKeyEnabled;
+        Locale mLocale;
+        int mOrientation;
+        int mWidth;
+        // KeyboardSet element id to element's parameters map.
+        final HashMap<Integer, ElementParams> mKeyboardSetElementIdToParamsMap =
+                new HashMap<Integer, ElementParams>();
+
+        static class ElementParams {
+            int mKeyboardXmlId;
+            boolean mProximityCharsCorrectionEnabled;
+        }
+    }
+
+    public static void clearKeyboardCache() {
+        sKeyboardCache.clear();
+        sKeysCache.clear();
+    }
+
+    private KeyboardSet(Context context, Params params) {
+        mContext = context;
+        mParams = params;
+    }
+
+    public Keyboard getKeyboard(int baseKeyboardSetElementId) {
+        final int keyboardSetElementId;
+        switch (mParams.mMode) {
+        case KeyboardId.MODE_PHONE:
+            if (baseKeyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS) {
+                keyboardSetElementId = KeyboardId.ELEMENT_PHONE_SYMBOLS;
+            } else {
+                keyboardSetElementId = KeyboardId.ELEMENT_PHONE;
+            }
+            break;
+        case KeyboardId.MODE_NUMBER:
+        case KeyboardId.MODE_DATE:
+        case KeyboardId.MODE_TIME:
+        case KeyboardId.MODE_DATETIME:
+            keyboardSetElementId = KeyboardId.ELEMENT_NUMBER;
+            break;
+        default:
+            keyboardSetElementId = baseKeyboardSetElementId;
+            break;
+        }
+
+        ElementParams elementParams = mParams.mKeyboardSetElementIdToParamsMap.get(
+                keyboardSetElementId);
+        if (elementParams == null) {
+            elementParams = mParams.mKeyboardSetElementIdToParamsMap.get(
+                    KeyboardId.ELEMENT_ALPHABET);
+        }
+        final KeyboardId id = getKeyboardId(keyboardSetElementId);
+        try {
+            return getKeyboard(mContext, elementParams, id);
+        } catch (RuntimeException e) {
+            throw new KeyboardSetException(e, id);
+        }
+    }
+
+    private Keyboard getKeyboard(Context context, ElementParams elementParams,
+            final KeyboardId id) {
+        final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
+        Keyboard keyboard = (ref == null) ? null : ref.get();
+        if (keyboard == null) {
+            final Keyboard.Builder<Keyboard.Params> builder =
+                    new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params());
+            if (id.isAlphabetKeyboard()) {
+                builder.setAutoGenerate(sKeysCache);
+            }
+            final int keyboardXmlId = elementParams.mKeyboardXmlId;
+            final RunInLocale<Void> job = new RunInLocale<Void>() {
+                @Override
+                protected Void job(Resources res) {
+                    builder.load(keyboardXmlId, id);
+                    return null;
+                }
+            };
+            job.runInLocale(context.getResources(), id.mLocale);
+            builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled);
+            builder.setProximityCharsCorrectionEnabled(
+                    elementParams.mProximityCharsCorrectionEnabled);
+            keyboard = builder.build();
+            sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard));
+
+            if (DEBUG_CACHE) {
+                Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": "
+                        + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
+            }
+        } else if (DEBUG_CACHE) {
+            Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": HIT  id=" + id);
+        }
+
+        return keyboard;
+    }
+
+    // Note: The keyboard for each locale, shift state, and mode are represented as KeyboardSet
+    // element id that is a key in keyboard_set.xml.  Also that file specifies which XML layout
+    // should be used for each keyboard.  The KeyboardId is an internal key for Keyboard object.
+    private KeyboardId getKeyboardId(int keyboardSetElementId) {
+        final Params params = mParams;
+        final boolean isSymbols = (keyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS
+                || keyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
+        final boolean voiceKeyEnabled = params.mVoiceKeyEnabled && !params.mDisableShortcutKey;
+        final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain);
+        return new KeyboardId(keyboardSetElementId, params.mLocale, params.mOrientation,
+                params.mWidth, params.mMode, params.mEditorInfo, params.mNoSettingsKey,
+                voiceKeyEnabled, hasShortcutKey, params.mLanguageSwitchKeyEnabled);
+    }
+
+    public static class Builder {
+        private final Context mContext;
+        private final String mPackageName;
+        private final Resources mResources;
+        private final EditorInfo mEditorInfo;
+
+        private final Params mParams = new Params();
+
+        private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
+
+        public Builder(Context context, EditorInfo editorInfo) {
+            mContext = context;
+            mPackageName = context.getPackageName();
+            mResources = context.getResources();
+            mEditorInfo = editorInfo;
+            final Params params = mParams;
+
+            params.mMode = getKeyboardMode(editorInfo);
+            params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO;
+            params.mNoSettingsKey = StringUtils.inPrivateImeOptions(
+                    mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo);
+        }
+
+        public Builder setScreenGeometry(int orientation, int widthPixels) {
+            mParams.mOrientation = orientation;
+            mParams.mWidth = widthPixels;
+            return this;
+        }
+
+        public Builder setSubtype(Locale inputLocale, boolean asciiCapable) {
+            final boolean deprecatedForceAscii = StringUtils.inPrivateImeOptions(
+                    mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo);
+            final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
+                    mParams.mEditorInfo.imeOptions)
+                    || deprecatedForceAscii;
+            mParams.mLocale = (forceAscii && !asciiCapable) ? Locale.US : inputLocale;
+            return this;
+        }
+
+        public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
+                boolean languageSwitchKeyEnabled) {
+            @SuppressWarnings("deprecation")
+            final boolean deprecatedNoMicrophone = StringUtils.inPrivateImeOptions(
+                    null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo);
+            final boolean noMicrophone = StringUtils.inPrivateImeOptions(
+                    mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo)
+                    || deprecatedNoMicrophone;
+            mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
+            mParams.mVoiceKeyOnMain = voiceKeyOnMain;
+            mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
+            return this;
+        }
+
+        public void setTouchPositionCorrectionEnabled(boolean enabled) {
+            mParams.mTouchPositionCorrectionEnabled = enabled;
+        }
+
+        public KeyboardSet build() {
+            if (mParams.mOrientation == Configuration.ORIENTATION_UNDEFINED)
+                throw new RuntimeException("Screen geometry is not specified");
+            if (mParams.mLocale == null)
+                throw new RuntimeException("KeyboardSet subtype is not specified");
+
+            final RunInLocale<Void> job = new RunInLocale<Void>() {
+                @Override
+                protected Void job(Resources res) {
+                    try {
+                        parseKeyboardSet(res, R.xml.keyboard_set);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e.getMessage() + " in "
+                                + res.getResourceName(R.xml.keyboard_set)
+                                + " of locale " + mParams.mLocale);
+                    }
+                    return null;
+                }
+            };
+            job.runInLocale(mResources, mParams.mLocale);
+            return new KeyboardSet(mContext, mParams);
+        }
+
+        private void parseKeyboardSet(Resources res, int resId) throws XmlPullParserException,
+                IOException {
+            final XmlResourceParser parser = res.getXml(resId);
+            try {
+                int event;
+                while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                    if (event == XmlPullParser.START_TAG) {
+                        final String tag = parser.getName();
+                        if (TAG_KEYBOARD_SET.equals(tag)) {
+                            final TypedArray a = mResources.obtainAttributes(
+                                    Xml.asAttributeSet(parser), R.styleable.KeyboardSet);
+                            mParams.mDisableShortcutKey = a.getBoolean(
+                                    R.styleable.KeyboardSet_disableShortcutKey, false);
+                            a.recycle();
+                            parseKeyboardSetContent(parser);
+                        } else {
+                            throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET);
+                        }
+                    }
+                }
+            } finally {
+                parser.close();
+            }
+        }
+
+        private void parseKeyboardSetContent(XmlPullParser parser) throws XmlPullParserException,
+                IOException {
+            int event;
+            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (event == XmlPullParser.START_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_ELEMENT.equals(tag)) {
+                        parseKeyboardSetElement(parser);
+                    } else {
+                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET);
+                    }
+                } else if (event == XmlPullParser.END_TAG) {
+                    final String tag = parser.getName();
+                    if (TAG_KEYBOARD_SET.equals(tag)) {
+                        break;
+                    } else {
+                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEYBOARD_SET);
+                    }
+                }
+            }
+        }
+
+        private void parseKeyboardSetElement(XmlPullParser parser) throws XmlPullParserException,
+                IOException {
+            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                    R.styleable.KeyboardSet_Element);
+            try {
+                XmlParseUtils.checkAttributeExists(a,
+                        R.styleable.KeyboardSet_Element_elementName, "elementName",
+                        TAG_ELEMENT, parser);
+                XmlParseUtils.checkAttributeExists(a,
+                        R.styleable.KeyboardSet_Element_elementKeyboard, "elementKeyboard",
+                        TAG_ELEMENT, parser);
+                XmlParseUtils.checkEndTag(TAG_ELEMENT, parser);
+
+                final ElementParams elementParams = new ElementParams();
+                final int elementName = a.getInt(
+                        R.styleable.KeyboardSet_Element_elementName, 0);
+                elementParams.mKeyboardXmlId = a.getResourceId(
+                        R.styleable.KeyboardSet_Element_elementKeyboard, 0);
+                elementParams.mProximityCharsCorrectionEnabled = a.getBoolean(
+                        R.styleable.KeyboardSet_Element_enableProximityCharsCorrection, false);
+                mParams.mKeyboardSetElementIdToParamsMap.put(elementName, elementParams);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        private static int getKeyboardMode(EditorInfo editorInfo) {
+            if (editorInfo == null)
+                return KeyboardId.MODE_TEXT;
+
+            final int inputType = editorInfo.inputType;
+            final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+
+            switch (inputType & InputType.TYPE_MASK_CLASS) {
+            case InputType.TYPE_CLASS_NUMBER:
+                return KeyboardId.MODE_NUMBER;
+            case InputType.TYPE_CLASS_DATETIME:
+                switch (variation) {
+                case InputType.TYPE_DATETIME_VARIATION_DATE:
+                    return KeyboardId.MODE_DATE;
+                case InputType.TYPE_DATETIME_VARIATION_TIME:
+                    return KeyboardId.MODE_TIME;
+                default: // InputType.TYPE_DATETIME_VARIATION_NORMAL
+                    return KeyboardId.MODE_DATETIME;
+                }
+            case InputType.TYPE_CLASS_PHONE:
+                return KeyboardId.MODE_PHONE;
+            case InputType.TYPE_CLASS_TEXT:
+                if (InputTypeUtils.isEmailVariation(variation)) {
+                    return KeyboardId.MODE_EMAIL;
+                } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
+                    return KeyboardId.MODE_URL;
+                } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+                    return KeyboardId.MODE_IM;
+                } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+                    return KeyboardId.MODE_TEXT;
+                } else {
+                    return KeyboardId.MODE_TEXT;
+                }
+            default:
+                return KeyboardId.MODE_TEXT;
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ac718fc..93d8704 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -18,10 +18,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.InflateException;
@@ -30,131 +27,66 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
-import com.android.inputmethod.keyboard.internal.ModifierKeyState;
-import com.android.inputmethod.keyboard.internal.ShiftKeyState;
+import com.android.inputmethod.keyboard.KeyboardSet.KeyboardSetException;
+import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.KeyboardState;
+import com.android.inputmethod.latin.DebugSettings;
 import com.android.inputmethod.latin.InputView;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.SettingsValues;
 import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.Utils;
 
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.Locale;
-
-public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class KeyboardSwitcher implements KeyboardState.SwitchActions {
     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
-    private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
-    public static final boolean DEBUG_STATE = false;
 
     public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
-    private static final int[] KEYBOARD_THEMES = {
-        R.style.KeyboardTheme,
-        R.style.KeyboardTheme_HighContrast,
-        R.style.KeyboardTheme_Stone,
-        R.style.KeyboardTheme_Stone_Bold,
-        R.style.KeyboardTheme_Gingerbread,
-        R.style.KeyboardTheme_IceCreamSandwich,
+
+    static class KeyboardTheme {
+        public final String mName;
+        public final int mThemeId;
+        public final int mStyleId;
+
+        public KeyboardTheme(String name, int themeId, int styleId) {
+            mName = name;
+            mThemeId = themeId;
+            mStyleId = styleId;
+        }
+    }
+
+    private static final KeyboardTheme[] KEYBOARD_THEMES = {
+        new KeyboardTheme("Basic",            0, R.style.KeyboardTheme),
+        new KeyboardTheme("HighContrast",     1, R.style.KeyboardTheme_HighContrast),
+        new KeyboardTheme("Stone",            6, R.style.KeyboardTheme_Stone),
+        new KeyboardTheme("Stne.Bold",        7, R.style.KeyboardTheme_Stone_Bold),
+        new KeyboardTheme("GingerBread",      8, R.style.KeyboardTheme_Gingerbread),
+        new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
     };
 
     private SubtypeSwitcher mSubtypeSwitcher;
     private SharedPreferences mPrefs;
+    private boolean mForceNonDistinctMultitouch;
 
     private InputView mCurrentInputView;
     private LatinKeyboardView mKeyboardView;
     private LatinIME mInputMethodService;
-    private String mPackageName;
     private Resources mResources;
 
-    // TODO: Combine these key state objects with auto mode switch state.
-    private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
-    private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
+    private KeyboardState mState;
 
-    private KeyboardId mMainKeyboardId;
-    private KeyboardId mSymbolsKeyboardId;
-    private KeyboardId mSymbolsShiftedKeyboardId;
-
-    private KeyboardId mCurrentId;
-    private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
-            new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
-
-    private KeyboardLayoutState mSavedKeyboardState = new KeyboardLayoutState();
+    private KeyboardSet mKeyboardSet;
 
     /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
      * what user actually typed. */
     private boolean mIsAutoCorrectionActive;
 
-    // TODO: Encapsulate these state handling to separate class and combine with ShiftKeyState
-    // and ModifierKeyState.
-    private static final int SWITCH_STATE_ALPHA = 0;
-    private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
-    private static final int SWITCH_STATE_SYMBOL = 2;
-    // The following states are used only on the distinct multi-touch panel devices.
-    private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
-    private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
-    private static final int SWITCH_STATE_CHORDING_ALPHA = 5;
-    private static final int SWITCH_STATE_CHORDING_SYMBOL = 6;
-    private int mSwitchState = SWITCH_STATE_ALPHA;
-
-    private static String mLayoutSwitchBackSymbols;
-
-    private int mThemeIndex = -1;
+    private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0];
     private Context mThemeContext;
 
     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
 
-    private class KeyboardLayoutState {
-        private boolean mIsValid;
-        private boolean mIsAlphabetMode;
-        private boolean mIsShiftLocked;
-        private boolean mIsShifted;
-
-        public void save() {
-            if (mCurrentId == null) {
-                return;
-            }
-            mIsAlphabetMode = isAlphabetMode();
-            if (mIsAlphabetMode) {
-                mIsShiftLocked = isShiftLocked();
-                mIsShifted = !mIsShiftLocked && isShiftedOrShiftLocked();
-            } else {
-                mIsShiftLocked = false;
-                mIsShifted = mCurrentId.equals(mSymbolsShiftedKeyboardId);
-            }
-            mIsValid = true;
-        }
-
-        public KeyboardId getKeyboardId() {
-            if (!mIsValid) return mMainKeyboardId;
-
-            if (mIsAlphabetMode) {
-                return mMainKeyboardId;
-            } else {
-                return mIsShifted ? mSymbolsShiftedKeyboardId : mSymbolsKeyboardId;
-            }
-        }
-
-        public void restore() {
-            if (!mIsValid) return;
-            mIsValid = false;
-
-            if (mIsAlphabetMode) {
-                final boolean isAlphabetMode = isAlphabetMode();
-                final boolean isShiftLocked = isAlphabetMode && isShiftLocked();
-                final boolean isShifted = !isShiftLocked && isShiftedOrShiftLocked();
-                if (mIsShiftLocked != isShiftLocked) {
-                    toggleCapsLock();
-                } else if (mIsShifted != isShifted) {
-                    onPressShift(false);
-                    onReleaseShift(false);
-                }
-            }
-        }
-    }
-
     public static KeyboardSwitcher getInstance() {
         return sInstance;
     }
@@ -169,52 +101,64 @@
 
     private void initInternal(LatinIME ims, SharedPreferences prefs) {
         mInputMethodService = ims;
-        mPackageName = ims.getPackageName();
         mResources = ims.getResources();
         mPrefs = prefs;
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-        setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
-        prefs.registerOnSharedPreferenceChangeListener(this);
+        mState = new KeyboardState(this);
+        setContextThemeWrapper(ims, getKeyboardTheme(ims, prefs));
+        mForceNonDistinctMultitouch = prefs.getBoolean(
+                DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
     }
 
-    private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) {
-        final String defaultThemeId = context.getString(R.string.config_default_keyboard_theme_id);
-        final String themeId = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultThemeId);
+    private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) {
+        final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index);
+        final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex);
         try {
-            final int themeIndex = Integer.valueOf(themeId);
-            if (themeIndex >= 0 && themeIndex < KEYBOARD_THEMES.length)
-                return themeIndex;
+            final int index = Integer.valueOf(themeIndex);
+            if (index >= 0 && index < KEYBOARD_THEMES.length) {
+                return KEYBOARD_THEMES[index];
+            }
         } catch (NumberFormatException e) {
             // Format error, keyboard theme is default to 0.
         }
-        Log.w(TAG, "Illegal keyboard theme in preference: " + themeId + ", default to 0");
-        return 0;
+        Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
+        return KEYBOARD_THEMES[0];
     }
 
-    private void setContextThemeWrapper(Context context, int themeIndex) {
-        if (mThemeIndex != themeIndex) {
-            mThemeIndex = themeIndex;
-            mThemeContext = new ContextThemeWrapper(context, KEYBOARD_THEMES[themeIndex]);
-            mKeyboardCache.clear();
+    private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) {
+        if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
+            mKeyboardTheme = keyboardTheme;
+            mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
+            KeyboardSet.clearKeyboardCache();
         }
     }
 
-    public void loadKeyboard(EditorInfo editorInfo, Settings.Values settingsValues) {
+    public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
+        final KeyboardSet.Builder builder = new KeyboardSet.Builder(mThemeContext, editorInfo);
+        builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation,
+                mThemeContext.getResources().getDisplayMetrics().widthPixels);
+        builder.setSubtype(
+                mSubtypeSwitcher.getInputLocale(),
+                mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
+                        LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE));
+        builder.setOptions(
+                settingsValues.isVoiceKeyEnabled(editorInfo),
+                settingsValues.isVoiceKeyOnMain(),
+                settingsValues.isLanguageSwitchKeyEnabled(mThemeContext));
+        mKeyboardSet = builder.build();
         try {
-            mMainKeyboardId = getKeyboardId(editorInfo, false, false, settingsValues);
-            mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues);
-            mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
-            mLayoutSwitchBackSymbols = mResources.getString(R.string.layout_switch_back_symbols);
-            setKeyboard(getKeyboard(mSavedKeyboardState.getKeyboardId()));
-            mSavedKeyboardState.restore();
-        } catch (RuntimeException e) {
-            Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
-            LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
+            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
+        } catch (KeyboardSetException e) {
+            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
+            LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
+            return;
         }
     }
 
     public void saveKeyboardState() {
-        mSavedKeyboardState.save();
+        if (getKeyboard() != null) {
+            mState.onSaveKeyboardState();
+        }
     }
 
     public void onFinishInputView() {
@@ -229,532 +173,174 @@
         final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
         mKeyboardView.setKeyboard(keyboard);
         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
-        mCurrentId = keyboard.mId;
-        mSwitchState = getSwitchState(mCurrentId);
-        updateShiftLockState(keyboard);
         mKeyboardView.setKeyPreviewPopupEnabled(
-                Settings.Values.isKeyPreviewPopupEnabled(mPrefs, mResources),
-                Settings.Values.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
-        final boolean localeChanged = (oldKeyboard == null)
+                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
+                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
+        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
+        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
+        final boolean subtypeChanged = (oldKeyboard == null)
                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
-        mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
-        updateShiftState();
+        final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
+                keyboard.mId.mLocale);
+        mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage);
     }
 
-    private int getSwitchState(KeyboardId id) {
-        return id.equals(mMainKeyboardId) ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN;
-    }
-
-    private void updateShiftLockState(Keyboard keyboard) {
-        if (mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
-            // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a.
-            // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
-            // that takes care of the current keyboard having such ALT key or not.
-            keyboard.setShiftLocked(keyboard.hasShiftLockKey());
-        } else if (mCurrentId.equals(mSymbolsKeyboardId)) {
-            // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
-            // indicator, we need to call setShiftLocked(false).
-            keyboard.setShiftLocked(false);
-        }
-    }
-
-    private LatinKeyboard getKeyboard(KeyboardId id) {
-        final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
-        LatinKeyboard keyboard = (ref == null) ? null : ref.get();
-        if (keyboard == null) {
-            final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, id.mLocale);
-            try {
-                final LatinKeyboard.Builder builder = new LatinKeyboard.Builder(mThemeContext);
-                builder.load(id);
-                builder.setTouchPositionCorrectionEnabled(
-                        mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
-                                LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
-                keyboard = builder.build();
-            } finally {
-                LocaleUtils.setSystemLocale(mResources, savedLocale);
-            }
-            mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
-
-            if (DEBUG_CACHE) {
-                Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
-                        + ((ref == null) ? "LOAD" : "GCed") + " id=" + id
-                        + " theme=" + Keyboard.themeName(keyboard.mThemeId));
-            }
-        } else if (DEBUG_CACHE) {
-            Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT  id=" + id
-                    + " theme=" + Keyboard.themeName(keyboard.mThemeId));
-        }
-
-        keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive);
-        keyboard.setShiftLocked(false);
-        keyboard.setShifted(false);
-        // If the cached keyboard had been switched to another keyboard while the language was
-        // displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
-        // we should reset the text fade factor. It is also applicable to shortcut key.
-        keyboard.setSpacebarTextFadeFactor(0.0f, null);
-        keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null);
-        return keyboard;
-    }
-
-    private KeyboardId getKeyboardId(EditorInfo editorInfo, final boolean isSymbols,
-            final boolean isShift, Settings.Values settingsValues) {
-        final int mode = Utils.getKeyboardMode(editorInfo);
-        final int xmlId;
-        switch (mode) {
-        case KeyboardId.MODE_PHONE:
-            xmlId = (isSymbols && isShift) ? R.xml.kbd_phone_shift : R.xml.kbd_phone;
-            break;
-        case KeyboardId.MODE_NUMBER:
-            xmlId = R.xml.kbd_number;
-            break;
-        default:
-            if (isSymbols) {
-                xmlId = isShift ? R.xml.kbd_symbols_shift : R.xml.kbd_symbols;
-            } else {
-                xmlId = R.xml.kbd_qwerty;
-            }
-            break;
-        }
-
-        final boolean settingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
-        @SuppressWarnings("deprecation")
-        final boolean noMicrophone = Utils.inPrivateImeOptions(
-                mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
-                || Utils.inPrivateImeOptions(
-                        null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
-        final boolean voiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo)
-                && !noMicrophone;
-        final boolean voiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
-        final boolean noSettingsKey = Utils.inPrivateImeOptions(
-                mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
-        final boolean hasSettingsKey = settingsKeyEnabled && !noSettingsKey;
-        final int f2KeyMode = getF2KeyMode(settingsKeyEnabled, noSettingsKey);
-        final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain);
-        final boolean forceAscii = Utils.inPrivateImeOptions(
-                mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
-        final boolean asciiCapable = mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
-                LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
-        final Locale locale = (forceAscii && !asciiCapable)
-                ? Locale.US : mSubtypeSwitcher.getInputLocale();
-        final Configuration conf = mResources.getConfiguration();
-        final DisplayMetrics dm = mResources.getDisplayMetrics();
-
-        return new KeyboardId(
-                mResources.getResourceEntryName(xmlId), xmlId, locale, conf.orientation,
-                dm.widthPixels, mode, editorInfo, hasSettingsKey, f2KeyMode, noSettingsKey,
-                voiceKeyEnabled, hasShortcutKey);
-    }
-
-    public int getKeyboardMode() {
-        return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT;
-    }
-
-    public boolean isAlphabetMode() {
-        return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
-    }
-
-    public boolean isInputViewShown() {
-        return mCurrentInputView != null && mCurrentInputView.isShown();
-    }
-
-    public boolean isKeyboardAvailable() {
-        if (mKeyboardView != null)
-            return mKeyboardView.getKeyboard() != null;
-        return false;
-    }
-
-    public LatinKeyboard getLatinKeyboard() {
+    public Keyboard getKeyboard() {
         if (mKeyboardView != null) {
-            final Keyboard keyboard = mKeyboardView.getKeyboard();
-            if (keyboard instanceof LatinKeyboard)
-                return (LatinKeyboard)keyboard;
+            return mKeyboardView.getKeyboard();
         }
         return null;
     }
 
-    public boolean isShiftedOrShiftLocked() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            return latinKeyboard.isShiftedOrShiftLocked();
-        return false;
-    }
-
-    public boolean isShiftLocked() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            return latinKeyboard.isShiftLocked();
-        return false;
-    }
-
-    private boolean isShiftLockShifted() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            return latinKeyboard.isShiftLockShifted();
-        return false;
-    }
-
-    public boolean isAutomaticTemporaryUpperCase() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            return latinKeyboard.isAutomaticTemporaryUpperCase();
-        return false;
-    }
-
-    public boolean isManualTemporaryUpperCase() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            return latinKeyboard.isManualTemporaryUpperCase();
-        return false;
-    }
-
-    private boolean isManualTemporaryUpperCaseFromAuto() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            return latinKeyboard.isManualTemporaryUpperCaseFromAuto();
-        return false;
-    }
-
-    private void setManualTemporaryUpperCase(boolean shifted) {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null) {
-            // On non-distinct multi touch panel device, we should also turn off the shift locked
-            // state when shift key is pressed to go to normal mode.
-            // On the other hand, on distinct multi touch panel device, turning off the shift locked
-            // state with shift key pressing is handled by onReleaseShift().
-            if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
-                latinKeyboard.setShiftLocked(false);
-            }
-            if (latinKeyboard.setShifted(shifted)) {
-                mKeyboardView.invalidateAllKeys();
-            }
-        }
-    }
-
-    private void setShiftLocked(boolean shiftLocked) {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
-            mKeyboardView.invalidateAllKeys();
-        }
-    }
-
-    /**
-     * Toggle keyboard shift state triggered by user touch event.
-     */
-    public void toggleShift() {
-        mInputMethodService.mHandler.cancelUpdateShiftState();
-        if (DEBUG_STATE)
-            Log.d(TAG, "toggleShift:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        if (isAlphabetMode()) {
-            setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
-        } else {
-            toggleShiftInSymbol();
-        }
-    }
-
-    public void toggleCapsLock() {
-        mInputMethodService.mHandler.cancelUpdateShiftState();
-        if (DEBUG_STATE)
-            Log.d(TAG, "toggleCapsLock:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        if (isAlphabetMode()) {
-            if (isShiftLocked()) {
-                // Shift key is long pressed while caps lock state, we will toggle back to normal
-                // state. And mark as if shift key is released.
-                setShiftLocked(false);
-                mShiftKeyState.onRelease();
-            } else {
-                setShiftLocked(true);
-            }
-        }
-    }
-
-    private void setAutomaticTemporaryUpperCase() {
-        if (mKeyboardView == null) return;
-        final Keyboard keyboard = mKeyboardView.getKeyboard();
-        if (keyboard == null) return;
-        keyboard.setAutomaticTemporaryUpperCase();
-        mKeyboardView.invalidateAllKeys();
-    }
-
     /**
      * Update keyboard shift state triggered by connected EditText status change.
      */
     public void updateShiftState() {
-        final ShiftKeyState shiftKeyState = mShiftKeyState;
-        if (DEBUG_STATE)
-            Log.d(TAG, "updateShiftState:"
-                    + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + shiftKeyState
-                    + " isAlphabetMode=" + isAlphabetMode()
-                    + " isShiftLocked=" + isShiftLocked());
-        if (isAlphabetMode()) {
-            if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
-                if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
-                    // Only when shift key is releasing, automatic temporary upper case will be set.
-                    setAutomaticTemporaryUpperCase();
-                } else {
-                    setManualTemporaryUpperCase(shiftKeyState.isMomentary());
-                }
-            }
-        } else {
-            // In symbol keyboard mode, we should clear shift key state because only alphabet
-            // keyboard has shift key.
-            shiftKeyState.onRelease();
+        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
+    }
+
+    public void onPressKey(int code) {
+        if (isVibrateAndSoundFeedbackRequired()) {
+            mInputMethodService.hapticAndAudioFeedback(code);
         }
+        mState.onPressKey(code);
     }
 
-    public void changeKeyboardMode() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "changeKeyboardMode:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        toggleKeyboardMode();
-        if (isShiftLocked() && isAlphabetMode())
-            setShiftLocked(true);
-        updateShiftState();
-    }
-
-    public void onPressShift(boolean withSliding) {
-        if (!isKeyboardAvailable())
-            return;
-        ShiftKeyState shiftKeyState = mShiftKeyState;
-        if (DEBUG_STATE)
-            Log.d(TAG, "onPressShift:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
-        if (isAlphabetMode()) {
-            if (isShiftLocked()) {
-                // Shift key is pressed while caps lock state, we will treat this state as shifted
-                // caps lock state and mark as if shift key pressed while normal state.
-                shiftKeyState.onPress();
-                setManualTemporaryUpperCase(true);
-            } else if (isAutomaticTemporaryUpperCase()) {
-                // Shift key is pressed while automatic temporary upper case, we have to move to
-                // manual temporary upper case.
-                shiftKeyState.onPress();
-                setManualTemporaryUpperCase(true);
-            } else if (isShiftedOrShiftLocked()) {
-                // In manual upper case state, we just record shift key has been pressing while
-                // shifted state.
-                shiftKeyState.onPressOnShifted();
-            } else {
-                // In base layout, chording or manual temporary upper case mode is started.
-                shiftKeyState.onPress();
-                toggleShift();
-            }
-        } else {
-            // In symbol mode, just toggle symbol and symbol more keyboard.
-            shiftKeyState.onPress();
-            toggleShift();
-            mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
-        }
-    }
-
-    public void onReleaseShift(boolean withSliding) {
-        if (!isKeyboardAvailable())
-            return;
-        ShiftKeyState shiftKeyState = mShiftKeyState;
-        if (DEBUG_STATE)
-            Log.d(TAG, "onReleaseShift:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
-        if (isAlphabetMode()) {
-            if (shiftKeyState.isMomentary()) {
-                // After chording input while normal state.
-                toggleShift();
-            } else if (isShiftLocked() && !isShiftLockShifted() && shiftKeyState.isPressing()
-                    && !withSliding) {
-                // Shift has been long pressed, ignore this release.
-            } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) {
-                // Shift has been pressed without chording while caps lock state.
-                toggleCapsLock();
-                // To be able to turn off caps lock by "double tap" on shift key, we should ignore
-                // the second tap of the "double tap" from now for a while because we just have
-                // already turned off caps lock above.
-                mKeyboardView.startIgnoringDoubleTap();
-            } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()
-                    && !withSliding) {
-                // Shift has been pressed without chording while shifted state.
-                toggleShift();
-            } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()
-                    && !withSliding) {
-                // Shift has been pressed without chording while manual temporary upper case
-                // transited from automatic temporary upper case.
-                toggleShift();
-            }
-        } else {
-            // In symbol mode, snap back to the previous keyboard mode if the user chords the shift
-            // key and another key, then releases the shift key.
-            if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
-                toggleShift();
-            }
-        }
-        shiftKeyState.onRelease();
-    }
-
-    public void onPressSymbol() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "onPressSymbol:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " symbolKeyState=" + mSymbolKeyState);
-        changeKeyboardMode();
-        mSymbolKeyState.onPress();
-        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
-    }
-
-    public void onReleaseSymbol() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "onReleaseSymbol:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " symbolKeyState=" + mSymbolKeyState);
-        // Snap back to the previous keyboard mode if the user chords the mode change key and
-        // another key, then releases the mode change key.
-        if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
-            changeKeyboardMode();
-        }
-        mSymbolKeyState.onRelease();
-    }
-
-    public void onOtherKeyPressed() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "onOtherKeyPressed:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState
-                    + " symbolKeyState=" + mSymbolKeyState);
-        mShiftKeyState.onOtherKeyPressed();
-        mSymbolKeyState.onOtherKeyPressed();
+    public void onReleaseKey(int code, boolean withSliding) {
+        mState.onReleaseKey(code, withSliding);
     }
 
     public void onCancelInput() {
-        // Snap back to the previous keyboard mode if the user cancels sliding input.
-        if (getPointerCount() == 1) {
-            if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
-                changeKeyboardMode();
-            } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
-                toggleShift();
-            }
+        mState.onCancelInput(isSinglePointer());
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetManualShiftedKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetAutomaticShiftedKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetShiftLockedKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetShiftLockShiftedKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsShiftedKeyboard() {
+        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void requestUpdatingShiftState() {
+        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void startDoubleTapTimer() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.startDoubleTapTimer();
         }
     }
 
-    private void toggleShiftInSymbol() {
-        if (isAlphabetMode())
-            return;
-        final LatinKeyboard keyboard;
-        if (mCurrentId.equals(mSymbolsKeyboardId)
-                || !mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
-            keyboard = getKeyboard(mSymbolsShiftedKeyboardId);
-        } else {
-            keyboard = getKeyboard(mSymbolsKeyboardId);
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void cancelDoubleTapTimer() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.cancelDoubleTapTimer();
         }
-        setKeyboard(keyboard);
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public boolean isInDoubleTapTimeout() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        return (keyboardView != null)
+                ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void startLongPressTimer(int code) {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.startLongPressTimer(code);
+        }
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void cancelLongPressTimer() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.cancelLongPressTimer();
+        }
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void hapticAndAudioFeedback(int code) {
+        mInputMethodService.hapticAndAudioFeedback(code);
+    }
+
+    public void onLongPressTimeout(int code) {
+        mState.onLongPressTimeout(code);
     }
 
     public boolean isInMomentarySwitchState() {
-        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
-                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+        return mState.isInMomentarySwitchState();
     }
 
-    public boolean isVibrateAndSoundFeedbackRequired() {
+    private boolean isVibrateAndSoundFeedbackRequired() {
         return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
     }
 
-    private int getPointerCount() {
-        return mKeyboardView == null ? 0 : mKeyboardView.getPointerCount();
-    }
-
-    private void toggleKeyboardMode() {
-        if (mCurrentId.equals(mMainKeyboardId)) {
-            setKeyboard(getKeyboard(mSymbolsKeyboardId));
-        } else {
-            setKeyboard(getKeyboard(mMainKeyboardId));
-        }
+    private boolean isSinglePointer() {
+        return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
     }
 
     public boolean hasDistinctMultitouch() {
         return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
     }
 
-    private static boolean isSpaceCharacter(int c) {
-        return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
-    }
-
-    private static boolean isLayoutSwitchBackCharacter(int c) {
-        if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
-        if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
-        return false;
-    }
-
     /**
-     * Updates state machine to figure out when to automatically snap back to the previous mode.
+     * Updates state machine to figure out when to automatically switch back to the previous mode.
      */
-    public void onKey(int code) {
-        if (DEBUG_STATE)
-            Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState
-                    + " pointers=" + getPointerCount());
-        switch (mSwitchState) {
-        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
-            // Only distinct multi touch devices can be in this state.
-            // On non-distinct multi touch devices, mode change key is handled by
-            // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
-            // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts
-            // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from
-            // {@link #SWITCH_STATE_MOMENTARY}.
-            if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
-                // Detected only the mode change key has been pressed, and then released.
-                if (mCurrentId.equals(mMainKeyboardId)) {
-                    mSwitchState = SWITCH_STATE_ALPHA;
-                } else {
-                    mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
-                }
-            } else if (getPointerCount() == 1) {
-                // Snap back to the previous keyboard mode if the user pressed the mode change key
-                // and slid to other key, then released the finger.
-                // If the user cancels the sliding input, snapping back to the previous keyboard
-                // mode is handled by {@link #onCancelInput}.
-                changeKeyboardMode();
-            } else {
-                // Chording input is being started. The keyboard mode will be snapped back to the
-                // previous mode in {@link onReleaseSymbol} when the mode change key is released.
-                mSwitchState = SWITCH_STATE_CHORDING_ALPHA;
-            }
-            break;
-        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
-            if (code == Keyboard.CODE_SHIFT) {
-                // Detected only the shift key has been pressed on symbol layout, and then released.
-                mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
-            } else if (getPointerCount() == 1) {
-                // Snap back to the previous keyboard mode if the user pressed the shift key on
-                // symbol mode and slid to other key, then released the finger.
-                toggleShift();
-                mSwitchState = SWITCH_STATE_SYMBOL;
-            } else {
-                // Chording input is being started. The keyboard mode will be snapped back to the
-                // previous mode in {@link onReleaseShift} when the shift key is released.
-                mSwitchState = SWITCH_STATE_CHORDING_SYMBOL;
-            }
-            break;
-        case SWITCH_STATE_SYMBOL_BEGIN:
-            if (!isSpaceCharacter(code) && code >= 0) {
-                mSwitchState = SWITCH_STATE_SYMBOL;
-            }
-            // Snap back to alpha keyboard mode immediately if user types a quote character.
-            if (isLayoutSwitchBackCharacter(code)) {
-                changeKeyboardMode();
-            }
-            break;
-        case SWITCH_STATE_SYMBOL:
-        case SWITCH_STATE_CHORDING_SYMBOL:
-            // Snap back to alpha keyboard mode if user types one or more non-space/enter
-            // characters followed by a space/enter or a quote character.
-            if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
-                changeKeyboardMode();
-            }
-            break;
-        }
+    public void onCodeInput(int code) {
+        mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState());
     }
 
     public LatinKeyboardView getKeyboardView() {
@@ -762,94 +348,52 @@
     }
 
     public View onCreateInputView() {
-        return createInputView(mThemeIndex, true);
-    }
-
-    private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
-        if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
-            return mCurrentInputView;
-
         if (mKeyboardView != null) {
             mKeyboardView.closing();
         }
 
-        final int oldThemeIndex = mThemeIndex;
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
             try {
-                setContextThemeWrapper(mInputMethodService, newThemeIndex);
+                setContextThemeWrapper(mInputMethodService, mKeyboardTheme);
                 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
                         R.layout.input_view, null);
                 tryGC = false;
             } catch (OutOfMemoryError e) {
                 Log.w(TAG, "load keyboard failed: " + e);
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
-                        oldThemeIndex + "," + newThemeIndex, e);
+                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
             } catch (InflateException e) {
                 Log.w(TAG, "load keyboard failed: " + e);
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
-                        oldThemeIndex + "," + newThemeIndex, e);
+                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
             }
         }
 
         mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
         mKeyboardView.setKeyboardActionListener(mInputMethodService);
+        if (mForceNonDistinctMultitouch) {
+            mKeyboardView.setDistinctMultitouch(false);
+        }
 
         // This always needs to be set since the accessibility state can
         // potentially change without the input view being re-created.
-        AccessibleKeyboardViewProxy.setView(mKeyboardView);
+        AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
 
         return mCurrentInputView;
     }
 
-    private void postSetInputView(final View newInputView) {
-        mInputMethodService.mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (newInputView != null) {
-                    mInputMethodService.setInputView(newInputView);
-                }
-                mInputMethodService.updateInputViewShown();
-            }
-        });
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
-            final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
-            postSetInputView(createInputView(themeIndex, false));
-        } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
-            postSetInputView(createInputView(mThemeIndex, true));
+    public void onNetworkStateChanged() {
+        if (mKeyboardView != null) {
+            mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady());
         }
     }
 
     public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
         if (mIsAutoCorrectionActive != isAutoCorrection) {
             mIsAutoCorrectionActive = isAutoCorrection;
-            final LatinKeyboard keyboard = getLatinKeyboard();
-            if (keyboard != null && keyboard.needsAutoCorrectionSpacebarLed()) {
-                final Key invalidatedKey = keyboard.onAutoCorrectionStateChanged(isAutoCorrection);
-                final LatinKeyboardView keyboardView = getKeyboardView();
-                if (keyboardView != null)
-                    keyboardView.invalidateKey(invalidatedKey);
+            if (mKeyboardView != null) {
+                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
             }
         }
     }
-
-    private static int getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey) {
-        if (noSettingsKey) {
-            // Never shows the Settings key
-            return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
-        }
-
-        if (settingsKeyEnabled) {
-            return KeyboardId.F2KEY_MODE_SETTINGS;
-        } else {
-            // It should be alright to fall back to the Settings key on 7-inch layouts
-            // even when the Settings key is not explicitly enabled.
-            return KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 04e6725..b51dbb9 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -38,28 +37,29 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
-import com.android.inputmethod.compat.FrameLayoutCompatUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.StringUtils;
 
 import java.util.HashMap;
+import java.util.HashSet;
 
 /**
  * A view that renders a virtual {@link Keyboard}.
  *
- * @attr ref R.styleable#KeyboardView_backgroundDimAmount
+ * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
  * @attr ref R.styleable#KeyboardView_keyBackground
  * @attr ref R.styleable#KeyboardView_keyLetterRatio
  * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
  * @attr ref R.styleable#KeyboardView_keyLabelRatio
  * @attr ref R.styleable#KeyboardView_keyHintLetterRatio
- * @attr ref R.styleable#KeyboardView_keyUppercaseLetterRatio
+ * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintRatio
  * @attr ref R.styleable#KeyboardView_keyHintLabelRatio
  * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
- * @attr ref R.styleable#KeyboardView_keyUppercaseLetterPadding
+ * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
  * @attr ref R.styleable#KeyboardView_keyTextStyle
  * @attr ref R.styleable#KeyboardView_keyPreviewLayout
  * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
@@ -69,8 +69,8 @@
  * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
  * @attr ref R.styleable#KeyboardView_keyHintLetterColor
  * @attr ref R.styleable#KeyboardView_keyHintLabelColor
- * @attr ref R.styleable#KeyboardView_keyUppercaseLetterInactivatedColor
- * @attr ref R.styleable#KeyboardView_keyUppercaseLetterActivatedColor
+ * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintInactivatedColor
+ * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintActivatedColor
  * @attr ref R.styleable#KeyboardView_shadowColor
  * @attr ref R.styleable#KeyboardView_shadowRadius
  */
@@ -81,7 +81,7 @@
     // XML attributes
     protected final float mVerticalCorrection;
     protected final int mMoreKeysLayout;
-    private final float mBackgroundDimAmount;
+    private final int mBackgroundDimAlpha;
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -94,15 +94,16 @@
     // The maximum key label width in the proportion to the key width.
     private static final float MAX_LABEL_RATIO = 0.90f;
 
+    private final static int ALPHA_OPAQUE = 255;
+
     // Main keyboard
     private Keyboard mKeyboard;
-    private final KeyDrawParams mKeyDrawParams;
+    protected final KeyDrawParams mKeyDrawParams;
 
     // Key preview
     private final int mKeyPreviewLayoutId;
     protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
     private boolean mShowKeyPreviewPopup = true;
-    private final int mDelayBeforePreview;
     private int mDelayAfterPreview;
     private ViewGroup mPreviewPlacer;
 
@@ -111,17 +112,18 @@
     private boolean mNeedsToDimBackground;
     /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
     private boolean mBufferNeedsUpdate;
-    /** The dirty region in the keyboard bitmap */
-    private final Rect mDirtyRect = new Rect();
-    /** The key to invalidate. */
-    private Key mInvalidatedKey;
-    /** The dirty region for single key drawing */
-    private final Rect mInvalidatedKeyRect = new Rect();
+    /** True if all keys should be drawn */
+    private boolean mInvalidateAllKeys;
+    /** The keys that should be drawn */
+    private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
+    /** The region of invalidated keys */
+    private final Rect mInvalidatedKeysRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
     private Bitmap mBuffer;
     /** The canvas for the above mutable keyboard bitmap */
     private Canvas mCanvas;
     private final Paint mPaint = new Paint();
+    private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
     // This map caches key label text height in pixel as value and key label text size as map key.
     private static final HashMap<Integer, Float> sTextHeightCache =
             new HashMap<Integer, Float>();
@@ -134,8 +136,7 @@
     private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
 
     public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
-        private static final int MSG_SHOW_KEY_PREVIEW = 1;
-        private static final int MSG_DISMISS_KEY_PREVIEW = 2;
+        private static final int MSG_DISMISS_KEY_PREVIEW = 1;
 
         public DrawingHandler(KeyboardView outerInstance) {
             super(outerInstance);
@@ -147,36 +148,12 @@
             if (keyboardView == null) return;
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
-            case MSG_SHOW_KEY_PREVIEW:
-                keyboardView.showKey(msg.arg1, tracker);
-                break;
             case MSG_DISMISS_KEY_PREVIEW:
                 tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
                 break;
             }
         }
 
-        public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
-            removeMessages(MSG_SHOW_KEY_PREVIEW);
-            final KeyboardView keyboardView = getOuterInstance();
-            if (keyboardView == null) return;
-            if (tracker.getKeyPreviewText().getVisibility() == VISIBLE || delay == 0) {
-                // Show right away, if it's already visible and finger is moving around
-                keyboardView.showKey(keyIndex, tracker);
-            } else {
-                sendMessageDelayed(
-                        obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
-            }
-        }
-
-        public void cancelShowKeyPreview(PointerTracker tracker) {
-            removeMessages(MSG_SHOW_KEY_PREVIEW, tracker);
-        }
-
-        public void cancelAllShowKeyPreviews() {
-            removeMessages(MSG_SHOW_KEY_PREVIEW);
-        }
-
         public void dismissKeyPreview(long delay, PointerTracker tracker) {
             sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
         }
@@ -190,12 +167,11 @@
         }
 
         public void cancelAllMessages() {
-            cancelAllShowKeyPreviews();
             cancelAllDismissKeyPreviews();
         }
     }
 
-    private static class KeyDrawParams {
+    protected static class KeyDrawParams {
         // XML attributes
         public final int mKeyTextColor;
         public final int mKeyTextInactivatedColor;
@@ -203,20 +179,20 @@
         public final float mKeyLabelHorizontalPadding;
         public final float mKeyHintLetterPadding;
         public final float mKeyPopupHintLetterPadding;
-        public final float mKeyUppercaseLetterPadding;
+        public final float mKeyShiftedLetterHintPadding;
         public final int mShadowColor;
         public final float mShadowRadius;
         public final Drawable mKeyBackground;
         public final int mKeyHintLetterColor;
         public final int mKeyHintLabelColor;
-        public final int mKeyUppercaseLetterInactivatedColor;
-        public final int mKeyUppercaseLetterActivatedColor;
+        public final int mKeyShiftedLetterHintInactivatedColor;
+        public final int mKeyShiftedLetterHintActivatedColor;
 
-        private final float mKeyLetterRatio;
+        /* package */ final float mKeyLetterRatio;
         private final float mKeyLargeLetterRatio;
         private final float mKeyLabelRatio;
         private final float mKeyHintLetterRatio;
-        private final float mKeyUppercaseLetterRatio;
+        private final float mKeyShiftedLetterHintRatio;
         private final float mKeyHintLabelRatio;
         private static final float UNDEFINED_RATIO = -1.0f;
 
@@ -225,8 +201,9 @@
         public int mKeyLargeLetterSize;
         public int mKeyLabelSize;
         public int mKeyHintLetterSize;
-        public int mKeyUppercaseLetterSize;
+        public int mKeyShiftedLetterHintSize;
         public int mKeyHintLabelSize;
+        public int mAnimAlpha;
 
         public KeyDrawParams(TypedArray a) {
             mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
@@ -244,8 +221,8 @@
             }
             mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
             mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
-            mKeyUppercaseLetterRatio = getRatio(a,
-                    R.styleable.KeyboardView_keyUppercaseLetterRatio);
+            mKeyShiftedLetterHintRatio = getRatio(a,
+                    R.styleable.KeyboardView_keyShiftedLetterHintRatio);
             mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
             mKeyLabelHorizontalPadding = a.getDimension(
                     R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
@@ -253,17 +230,17 @@
                     R.styleable.KeyboardView_keyHintLetterPadding, 0);
             mKeyPopupHintLetterPadding = a.getDimension(
                     R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
-            mKeyUppercaseLetterPadding = a.getDimension(
-                    R.styleable.KeyboardView_keyUppercaseLetterPadding, 0);
+            mKeyShiftedLetterHintPadding = a.getDimension(
+                    R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
             mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
             mKeyTextInactivatedColor = a.getColor(
                     R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
             mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
             mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
-            mKeyUppercaseLetterInactivatedColor = a.getColor(
-                    R.styleable.KeyboardView_keyUppercaseLetterInactivatedColor, 0);
-            mKeyUppercaseLetterActivatedColor = a.getColor(
-                    R.styleable.KeyboardView_keyUppercaseLetterActivatedColor, 0);
+            mKeyShiftedLetterHintInactivatedColor = a.getColor(
+                    R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
+            mKeyShiftedLetterHintActivatedColor = a.getColor(
+                    R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
             mKeyTextStyle = Typeface.defaultFromStyle(
                     a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
             mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
@@ -279,12 +256,18 @@
                 mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
             mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
             mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
-            mKeyUppercaseLetterSize = (int)(keyHeight * mKeyUppercaseLetterRatio);
+            mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio);
             mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
         }
+
+        public void brendAlpha(Paint paint) {
+            final int color = paint.getColor();
+            paint.setARGB((paint.getAlpha() * mAnimAlpha) / ALPHA_OPAQUE,
+                    Color.red(color), Color.green(color), Color.blue(color));
+        }
     }
 
-    protected static class KeyPreviewDrawParams {
+    /* package */ static class KeyPreviewDrawParams {
         // XML attributes.
         public final Drawable mPreviewBackground;
         public final Drawable mPreviewLeftBackground;
@@ -295,6 +278,7 @@
         public final int mPreviewOffset;
         public final int mPreviewHeight;
         public final Typeface mKeyTextStyle;
+        public final int mLingerTimeout;
 
         private final float mPreviewTextRatio;
         private final float mKeyLetterRatio;
@@ -324,6 +308,7 @@
                     R.styleable.KeyboardView_keyPreviewHeight, 80);
             mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
             mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
+            mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
 
             mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
             mKeyTextStyle = keyDrawParams.mKeyTextStyle;
@@ -360,21 +345,16 @@
         mVerticalCorrection = a.getDimensionPixelOffset(
                 R.styleable.KeyboardView_verticalCorrection, 0);
         mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
-        mBackgroundDimAmount = a.getFloat(R.styleable.KeyboardView_backgroundDimAmount, 0.5f);
+        mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
         a.recycle();
 
-        final Resources res = getResources();
-
-        mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
-        mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
+        mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
 
         mPaint.setAntiAlias(true);
-        mPaint.setTextAlign(Align.CENTER);
-        mPaint.setAlpha(255);
     }
 
     // Read fraction value in TypedArray as float.
-    private static float getRatio(TypedArray a, int index) {
+    /* package */ static float getRatio(TypedArray a, int index) {
         return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
     }
 
@@ -386,16 +366,14 @@
      * @param keyboard the keyboard to display in this view
      */
     public void setKeyboard(Keyboard keyboard) {
-        // Remove any pending dismissing preview
-        mDrawingHandler.cancelAllShowKeyPreviews();
+        // Remove any pending messages.
+        mDrawingHandler.cancelAllMessages();
         if (mKeyboard != null) {
             PointerTracker.dismissAllKeyPreviews();
         }
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
         requestLayout();
-        mDirtyRect.set(0, 0, getWidth(), getHeight());
-        mBufferNeedsUpdate = true;
         invalidateAllKeys();
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mKeyDrawParams.updateKeyHeight(keyHeight);
@@ -462,49 +440,50 @@
             if (mBuffer != null)
                 mBuffer.recycle();
             mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            mDirtyRect.union(0, 0, width, height);
+            mInvalidateAllKeys = true;
             if (mCanvas != null) {
                 mCanvas.setBitmap(mBuffer);
             } else {
                 mCanvas = new Canvas(mBuffer);
             }
         }
-        final Canvas canvas = mCanvas;
-        canvas.clipRect(mDirtyRect, Op.REPLACE);
-        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
 
         if (mKeyboard == null) return;
 
-        final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
+        final Canvas canvas = mCanvas;
+        final Paint paint = mPaint;
         final KeyDrawParams params = mKeyDrawParams;
-        if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) {
-            // Draw a single key.
-            final int keyDrawX = mInvalidatedKey.mX + mInvalidatedKey.mVisualInsetsLeft
-                    + getPaddingLeft();
-            final int keyDrawY = mInvalidatedKey.mY + getPaddingTop();
-            canvas.translate(keyDrawX, keyDrawY);
-            onBufferDrawKey(mInvalidatedKey, mKeyboard, canvas, mPaint, params,
-                    isManualTemporaryUpperCase);
-            canvas.translate(-keyDrawX, -keyDrawY);
-        } else {
+
+        if (mInvalidateAllKeys || mInvalidatedKeys.isEmpty()) {
+            mInvalidatedKeysRect.set(0, 0, getWidth(), getHeight());
+            canvas.clipRect(mInvalidatedKeysRect, Op.REPLACE);
+            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
             // Draw all keys.
             for (final Key key : mKeyboard.mKeys) {
-                final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
-                final int keyDrawY = key.mY + getPaddingTop();
-                canvas.translate(keyDrawX, keyDrawY);
-                onBufferDrawKey(key, mKeyboard, canvas, mPaint, params, isManualTemporaryUpperCase);
-                canvas.translate(-keyDrawX, -keyDrawY);
+                onDrawKey(key, canvas, paint, params);
+            }
+        } else {
+            // Draw invalidated keys.
+            for (final Key key : mInvalidatedKeys) {
+                final int x = key.mX + getPaddingLeft();
+                final int y = key.mY + getPaddingTop();
+                mInvalidatedKeysRect.set(x, y, x + key.mWidth, y + key.mHeight);
+                canvas.clipRect(mInvalidatedKeysRect, Op.REPLACE);
+                canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+                onDrawKey(key, canvas, paint, params);
             }
         }
 
         // Overlay a dark rectangle to dim the entire keyboard
         if (mNeedsToDimBackground) {
-            mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
-            canvas.drawRect(0, 0, width, height, mPaint);
+            paint.setColor(Color.BLACK);
+            paint.setAlpha(mBackgroundDimAlpha);
+            canvas.drawRect(0, 0, width, height, paint);
         }
 
-        mInvalidatedKey = null;
-        mDirtyRect.setEmpty();
+        mInvalidatedKeys.clear();
+        mInvalidatedKeysRect.setEmpty();
+        mInvalidateAllKeys = false;
     }
 
     public void dimEntireKeyboard(boolean dimmed) {
@@ -515,47 +494,61 @@
         }
     }
 
-    private static void onBufferDrawKey(final Key key, final Keyboard keyboard, final Canvas canvas,
-            Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) {
-        final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG;
-        // Draw key background.
-        if (!key.isSpacer()) {
-            final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
-                    + params.mPadding.left + params.mPadding.right;
-            final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
-            final int bgX = -params.mPadding.left;
-            final int bgY = -params.mPadding.top;
-            final int[] drawableState = key.getCurrentDrawableState();
-            final Drawable background = params.mKeyBackground;
-            background.setState(drawableState);
-            final Rect bounds = background.getBounds();
-            if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
-                background.setBounds(0, 0, bgWidth, bgHeight);
-            }
-            canvas.translate(bgX, bgY);
-            background.draw(canvas);
-            if (debugShowAlign) {
-                drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
-            }
-            canvas.translate(-bgX, -bgY);
-        }
+    private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
+        final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
+        final int keyDrawY = key.mY + getPaddingTop();
+        canvas.translate(keyDrawX, keyDrawY);
 
-        // Draw key top visuals.
+        params.mAnimAlpha = ALPHA_OPAQUE;
+        if (!key.isSpacer()) {
+            onDrawKeyBackground(key, canvas, params);
+        }
+        onDrawKeyTopVisuals(key, canvas, paint, params);
+
+        canvas.translate(-keyDrawX, -keyDrawY);
+    }
+
+    // Draw key background.
+    protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
+        final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
+                + params.mPadding.left + params.mPadding.right;
+        final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
+        final int bgX = -params.mPadding.left;
+        final int bgY = -params.mPadding.top;
+        final int[] drawableState = key.getCurrentDrawableState();
+        final Drawable background = params.mKeyBackground;
+        background.setState(drawableState);
+        final Rect bounds = background.getBounds();
+        if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
+            background.setBounds(0, 0, bgWidth, bgHeight);
+        }
+        canvas.translate(bgX, bgY);
+        background.draw(canvas);
+        if (LatinImeLogger.sVISUALDEBUG) {
+            drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
+        }
+        canvas.translate(-bgX, -bgY);
+    }
+
+    // Draw key top visuals.
+    protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
         final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
         final int keyHeight = key.mHeight;
         final float centerX = keyWidth * 0.5f;
         final float centerY = keyHeight * 0.5f;
 
-        if (debugShowAlign) {
+        if (LatinImeLogger.sVISUALDEBUG) {
             drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint());
         }
 
         // Draw key label.
-        final Drawable icon = key.getIcon();
+        final Drawable icon = key.getIcon(mKeyboard.mIconsSet);
+        if (icon != null) {
+            icon.setAlpha(params.mAnimAlpha);
+        }
         float positionX = centerX;
         if (key.mLabel != null) {
-            // Switch the character to uppercase if shift is pressed
-            final CharSequence label = keyboard.adjustLabelCase(key.mLabel);
+            final String label = key.mLabel;
             // For characters, use large font. For labels like "Done", use smaller font.
             paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
             final int labelSize = key.selectTextSize(params.mKeyLetterSize,
@@ -598,11 +591,8 @@
                         Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
             }
 
-            if (key.hasUppercaseLetter() && isManualTemporaryUpperCase) {
-                paint.setColor(params.mKeyTextInactivatedColor);
-            } else {
-                paint.setColor(params.mKeyTextColor);
-            }
+            paint.setColor(key.isShiftedLetterActivated()
+                    ? params.mKeyTextInactivatedColor : params.mKeyTextColor);
             if (key.isEnabled()) {
                 // Set a drop shadow for the text
                 paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor);
@@ -610,6 +600,7 @@
                 // Make label invisible
                 paint.setColor(Color.TRANSPARENT);
             }
+            params.brendAlpha(paint);
             canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
             // Turn off drop shadow and reset x-scale.
             paint.setShadowLayer(0, 0, 0, 0);
@@ -628,7 +619,7 @@
                 }
             }
 
-            if (debugShowAlign) {
+            if (LatinImeLogger.sVISUALDEBUG) {
                 final Paint line = new Paint();
                 drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
                 drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line);
@@ -637,23 +628,24 @@
 
         // Draw hint label.
         if (key.mHintLabel != null) {
-            final CharSequence hint = key.mHintLabel;
+            final String hint = key.mHintLabel;
             final int hintColor;
             final int hintSize;
             if (key.hasHintLabel()) {
                 hintColor = params.mKeyHintLabelColor;
                 hintSize = params.mKeyHintLabelSize;
                 paint.setTypeface(Typeface.DEFAULT);
-            } else if (key.hasUppercaseLetter()) {
-                hintColor = isManualTemporaryUpperCase
-                        ? params.mKeyUppercaseLetterActivatedColor
-                        : params.mKeyUppercaseLetterInactivatedColor;
-                hintSize = params.mKeyUppercaseLetterSize;
+            } else if (key.hasShiftedLetterHint()) {
+                hintColor = key.isShiftedLetterActivated()
+                        ? params.mKeyShiftedLetterHintActivatedColor
+                        : params.mKeyShiftedLetterHintInactivatedColor;
+                hintSize = params.mKeyShiftedLetterHintSize;
             } else { // key.hasHintLetter()
                 hintColor = params.mKeyHintLetterColor;
                 hintSize = params.mKeyHintLetterSize;
             }
             paint.setColor(hintColor);
+            params.brendAlpha(paint);
             paint.setTextSize(hintSize);
             final float hintX, hintY;
             if (key.hasHintLabel()) {
@@ -663,22 +655,23 @@
                 hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2;
                 hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
                 paint.setTextAlign(Align.LEFT);
-            } else if (key.hasUppercaseLetter()) {
+            } else if (key.hasShiftedLetterHint()) {
                 // The hint label is placed at top-right corner of the key. Used mainly on tablet.
-                hintX = keyWidth - params.mKeyUppercaseLetterPadding
+                hintX = keyWidth - params.mKeyShiftedLetterHintPadding
                         - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
-                hintY = -paint.ascent();
+                paint.getFontMetrics(mFontMetrics);
+                hintY = -mFontMetrics.top + params.mKeyShiftedLetterHintPadding;
                 paint.setTextAlign(Align.CENTER);
             } else { // key.hasHintLetter()
                 // The hint label is placed at top-right corner of the key. Used mainly on phone.
                 hintX = keyWidth - params.mKeyHintLetterPadding
                         - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
-                hintY = -paint.ascent();
+                hintY = -paint.ascent() + params.mKeyHintLetterPadding;
                 paint.setTextAlign(Align.CENTER);
             }
             canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint);
 
-            if (debugShowAlign) {
+            if (LatinImeLogger.sVISUALDEBUG) {
                 final Paint line = new Paint();
                 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
                 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
@@ -703,38 +696,43 @@
             }
             drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
 
-            if (debugShowAlign) {
+            if (LatinImeLogger.sVISUALDEBUG) {
                 final Paint line = new Paint();
                 drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line);
                 drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line);
             }
         }
 
-        // Draw popup hint "..." at the bottom right corner of the key.
-        if ((key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0)
-                || key.needsSpecialPopupHint()) {
-            paint.setTextSize(params.mKeyHintLetterSize);
-            paint.setColor(params.mKeyHintLabelColor);
-            paint.setTextAlign(Align.CENTER);
-            final float hintX = keyWidth - params.mKeyHintLetterPadding
-                    - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
-            final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
-            canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
-
-            if (debugShowAlign) {
-                final Paint line = new Paint();
-                drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
-                drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
-            }
+        if (key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0) {
+            drawKeyPopupHint(key, canvas, paint, params);
         }
     }
 
-    private static final Rect sTextBounds = new Rect();
+    // Draw popup hint "..." at the bottom right corner of the key.
+    protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
+        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyHeight = key.mHeight;
 
-    private static int getCharGeometryCacheKey(char reference, Paint paint) {
+        paint.setTextSize(params.mKeyHintLetterSize);
+        paint.setColor(params.mKeyHintLabelColor);
+        params.brendAlpha(paint);
+        paint.setTextAlign(Align.CENTER);
+        final float hintX = keyWidth - params.mKeyHintLetterPadding
+                - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+        final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
+        canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
+
+        if (LatinImeLogger.sVISUALDEBUG) {
+            final Paint line = new Paint();
+            drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
+            drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
+        }
+    }
+
+    private static int getCharGeometryCacheKey(char referenceChar, Paint paint) {
         final int labelSize = (int)paint.getTextSize();
         final Typeface face = paint.getTypeface();
-        final int codePointOffset = reference << 15;
+        final int codePointOffset = referenceChar << 15;
         if (face == Typeface.DEFAULT) {
             return codePointOffset + labelSize;
         } else if (face == Typeface.DEFAULT_BOLD) {
@@ -746,42 +744,39 @@
         }
     }
 
-    private static float getCharHeight(char[] character, Paint paint) {
-        final Integer key = getCharGeometryCacheKey(character[0], paint);
+    // Working variable for the following methods.
+    private final Rect mTextBounds = new Rect();
+
+    private float getCharHeight(char[] referenceChar, Paint paint) {
+        final Integer key = getCharGeometryCacheKey(referenceChar[0], paint);
         final Float cachedValue = sTextHeightCache.get(key);
         if (cachedValue != null)
             return cachedValue;
 
-        paint.getTextBounds(character, 0, 1, sTextBounds);
-        final float height = sTextBounds.height();
+        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
+        final float height = mTextBounds.height();
         sTextHeightCache.put(key, height);
         return height;
     }
 
-    private static float getCharWidth(char[] character, Paint paint) {
-        final Integer key = getCharGeometryCacheKey(character[0], paint);
+    private float getCharWidth(char[] referenceChar, Paint paint) {
+        final Integer key = getCharGeometryCacheKey(referenceChar[0], paint);
         final Float cachedValue = sTextWidthCache.get(key);
         if (cachedValue != null)
             return cachedValue;
 
-        paint.getTextBounds(character, 0, 1, sTextBounds);
-        final float width = sTextBounds.width();
+        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
+        final float width = mTextBounds.width();
         sTextWidthCache.put(key, width);
         return width;
     }
 
-    private static float getLabelWidth(CharSequence label, Paint paint) {
-        paint.getTextBounds(label.toString(), 0, label.length(), sTextBounds);
-        return sTextBounds.width();
+    public float getLabelWidth(String label, Paint paint) {
+        paint.getTextBounds(label.toString(), 0, label.length(), mTextBounds);
+        return mTextBounds.width();
     }
 
-    public float getDefaultLabelWidth(CharSequence label, Paint paint) {
-        paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
-        paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
-        return getLabelWidth(label, paint);
-    }
-
-    private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width,
+    protected static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width,
             int height) {
         canvas.translate(x, y);
         icon.setBounds(0, 0, width, height);
@@ -814,6 +809,14 @@
         canvas.translate(-x, -y);
     }
 
+    public Paint newDefaultLabelPaint() {
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
+        paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
+        return paint;
+    }
+
     public void cancelAllMessages() {
         mDrawingHandler.cancelAllMessages();
     }
@@ -830,20 +833,14 @@
     }
 
     @Override
-    public void showKeyPreview(int keyIndex, PointerTracker tracker) {
+    public void showKeyPreview(PointerTracker tracker) {
         if (mShowKeyPreviewPopup) {
-            mDrawingHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
+            showKey(tracker);
         }
     }
 
     @Override
-    public void cancelShowKeyPreview(PointerTracker tracker) {
-        mDrawingHandler.cancelShowKeyPreview(tracker);
-    }
-
-    @Override
     public void dismissKeyPreview(PointerTracker tracker) {
-        mDrawingHandler.cancelShowKeyPreview(tracker);
         mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
     }
 
@@ -855,10 +852,10 @@
             windowContentView.addView(mPreviewPlacer);
         }
         mPreviewPlacer.addView(
-                keyPreview, FrameLayoutCompatUtils.newLayoutParam(mPreviewPlacer, 0, 0));
+                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacer, 0, 0));
     }
 
-    private void showKey(final int keyIndex, PointerTracker tracker) {
+    private void showKey(PointerTracker tracker) {
         final TextView previewText = tracker.getKeyPreviewText();
         // If the key preview has no parent view yet, add it to the ViewGroup which can place
         // key preview absolutely in SoftInputWindow.
@@ -867,8 +864,8 @@
         }
 
         mDrawingHandler.cancelDismissKeyPreview(tracker);
-        final Key key = tracker.getKey(keyIndex);
-        // If keyIndex is invalid or IME is already closed, we must not show key preview.
+        final Key key = tracker.getKey();
+        // If key is invalid or IME is already closed, we must not show key preview.
         // Trying to show key preview while root window is closed causes
         // WindowManager.BadTokenException.
         if (key == null)
@@ -877,22 +874,21 @@
         final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
         final int keyDrawX = key.mX + key.mVisualInsetsLeft;
         final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
-        // What we show as preview should match what we show on key top in onBufferDraw(). 
+        // What we show as preview should match what we show on a key top in onBufferDraw().
         if (key.mLabel != null) {
             // TODO Should take care of temporaryShiftLabel here.
             previewText.setCompoundDrawables(null, null, null, null);
-            if (key.mLabel.length() > 1) {
+            if (StringUtils.codePointCount(key.mLabel) > 1) {
                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
                 previewText.setTypeface(Typeface.DEFAULT_BOLD);
             } else {
                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
                 previewText.setTypeface(params.mKeyTextStyle);
             }
-            previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
+            previewText.setText(key.mLabel);
         } else {
-            final Drawable previewIcon = key.getPreviewIcon();
             previewText.setCompoundDrawables(null, null, null,
-                   previewIcon != null ? previewIcon : key.getIcon());
+                    key.getPreviewIcon(mKeyboard.mIconsSet));
             previewText.setText(null);
         }
         previewText.setBackgroundDrawable(params.mPreviewBackground);
@@ -906,19 +902,23 @@
         int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0];
         final int previewY = key.mY - previewHeight
                 + params.mCoordinates[1] + params.mPreviewOffset;
-        if (previewX < 0 && params.mPreviewLeftBackground != null) {
-            previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
+        if (previewX < 0) {
             previewX = 0;
-        } else if (previewX + previewWidth > getWidth() && params.mPreviewRightBackground != null) {
-            previewText.setBackgroundDrawable(params.mPreviewRightBackground);
+            if (params.mPreviewLeftBackground != null) {
+                previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
+            }
+        } else if (previewX > getWidth() - previewWidth) {
             previewX = getWidth() - previewWidth;
+            if (params.mPreviewRightBackground != null) {
+                previewText.setBackgroundDrawable(params.mPreviewRightBackground);
+            }
         }
 
         // Set the preview background state
         previewText.getBackground().setState(
                 key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
         previewText.setTextColor(params.mPreviewTextColor);
-        FrameLayoutCompatUtils.placeViewAt(
+        ViewLayoutUtils.placeViewAt(
                 previewText, previewX, previewY, previewWidth, previewHeight);
         previewText.setVisibility(VISIBLE);
     }
@@ -930,7 +930,8 @@
      * @see #invalidateKey(Key)
      */
     public void invalidateAllKeys() {
-        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mInvalidatedKeys.clear();
+        mInvalidateAllKeys = true;
         mBufferNeedsUpdate = true;
         invalidate();
     }
@@ -944,22 +945,21 @@
      */
     @Override
     public void invalidateKey(Key key) {
-        if (key == null)
-            return;
-        mInvalidatedKey = key;
+        if (mInvalidateAllKeys) return;
+        if (key == null) return;
+        mInvalidatedKeys.add(key);
         final int x = key.mX + getPaddingLeft();
         final int y = key.mY + getPaddingTop();
-        mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight);
-        mDirtyRect.union(mInvalidatedKeyRect);
+        mInvalidatedKeysRect.union(x, y, x + key.mWidth, y + key.mHeight);
         mBufferNeedsUpdate = true;
-        invalidate(mInvalidatedKeyRect);
+        invalidate(mInvalidatedKeysRect);
     }
 
     public void closing() {
         PointerTracker.dismissAllKeyPreviews();
         cancelAllMessages();
 
-        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mInvalidateAllKeys = true;
         requestLayout();
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
deleted file mode 100644
index 7620396..0000000
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
-import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Locale;
-
-// TODO: We should remove this class
-public class LatinKeyboard extends Keyboard {
-    private static final int SPACE_LED_LENGTH_PERCENT = 80;
-
-    private final Resources mRes;
-    private final Theme mTheme;
-    private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-
-    /* Space key and its icons, drawables and colors. */
-    private final Key mSpaceKey;
-    private final Drawable mSpaceIcon;
-    private final boolean mAutoCorrectionSpacebarLedEnabled;
-    private final Drawable mAutoCorrectionSpacebarLedIcon;
-    private final int mSpacebarTextColor;
-    private final int mSpacebarTextShadowColor;
-    private float mSpacebarTextFadeFactor = 0.0f;
-    private final HashMap<Integer, BitmapDrawable> mSpaceDrawableCache =
-            new HashMap<Integer, BitmapDrawable>();
-    private final boolean mIsSpacebarTriggeringPopupByLongPress;
-
-    /* Shortcut key and its icons if available */
-    private final Key mShortcutKey;
-    private final Drawable mEnabledShortcutIcon;
-    private final Drawable mDisabledShortcutIcon;
-
-    // Height in space key the language name will be drawn. (proportional to space key height)
-    public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
-    // If the full language name needs to be smaller than this value to be drawn on space key,
-    // its short language name will be used instead.
-    private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
-
-    private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small";
-    private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
-
-    private LatinKeyboard(Context context, LatinKeyboardParams params) {
-        super(params);
-        mRes = context.getResources();
-        mTheme = context.getTheme();
-
-        // The index of space key is available only after Keyboard constructor has finished.
-        mSpaceKey = params.mSpaceKey;
-        mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
-
-        mShortcutKey = params.mShortcutKey;
-        mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
-        final int longPressSpaceKeyTimeout =
-                mRes.getInteger(R.integer.config_long_press_space_key_timeout);
-        mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0);
-
-        final TypedArray a = context.obtainStyledAttributes(
-                null, R.styleable.LatinKeyboard, R.attr.latinKeyboardStyle, R.style.LatinKeyboard);
-        mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
-                R.styleable.LatinKeyboard_autoCorrectionSpacebarLedEnabled, false);
-        mAutoCorrectionSpacebarLedIcon = a.getDrawable(
-                R.styleable.LatinKeyboard_autoCorrectionSpacebarLedIcon);
-        mDisabledShortcutIcon = a.getDrawable(R.styleable.LatinKeyboard_disabledShortcutIcon);
-        mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboard_spacebarTextColor, 0);
-        mSpacebarTextShadowColor = a.getColor(
-                R.styleable.LatinKeyboard_spacebarTextShadowColor, 0);
-        a.recycle();
-    }
-
-    private static class LatinKeyboardParams extends KeyboardParams {
-        public Key mSpaceKey = null;
-        public Key mShortcutKey = null;
-
-        @Override
-        public void onAddKey(Key key) {
-            super.onAddKey(key);
-
-            switch (key.mCode) {
-            case Keyboard.CODE_SPACE:
-                mSpaceKey = key;
-                break;
-            case Keyboard.CODE_SHORTCUT:
-                mShortcutKey = key;
-                break;
-            }
-        }
-    }
-
-    public static class Builder extends KeyboardBuilder<LatinKeyboardParams> {
-        public Builder(Context context) {
-            super(context, new LatinKeyboardParams());
-        }
-
-        @Override
-        public Builder load(KeyboardId id) {
-            super.load(id);
-            return this;
-        }
-
-        @Override
-        public LatinKeyboard build() {
-            return new LatinKeyboard(mContext, mParams);
-        }
-    }
-
-    public void setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view) {
-        mSpacebarTextFadeFactor = fadeFactor;
-        updateSpacebarForLocale(false);
-        if (view != null)
-            view.invalidateKey(mSpaceKey);
-    }
-
-    private static int getSpacebarTextColor(int color, float fadeFactor) {
-        final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
-                Color.red(color), Color.green(color), Color.blue(color));
-        return newColor;
-    }
-
-    public void updateShortcutKey(boolean available, KeyboardView view) {
-        if (mShortcutKey == null)
-            return;
-        mShortcutKey.setEnabled(available);
-        mShortcutKey.setIcon(available ? mEnabledShortcutIcon : mDisabledShortcutIcon);
-        if (view != null)
-            view.invalidateKey(mShortcutKey);
-    }
-
-    public boolean needsAutoCorrectionSpacebarLed() {
-        return mAutoCorrectionSpacebarLedEnabled;
-    }
-
-    /**
-     * @return a key which should be invalidated.
-     */
-    public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) {
-        updateSpacebarForLocale(isAutoCorrection);
-        return mSpaceKey;
-    }
-
-    @Override
-    public CharSequence adjustLabelCase(CharSequence label) {
-        if (isAlphaKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label)
-                && label.length() < 3 && Character.isLowerCase(label.charAt(0))) {
-            return label.toString().toUpperCase(mId.mLocale);
-        }
-        return label;
-    }
-
-    private void updateSpacebarForLocale(boolean isAutoCorrection) {
-        if (mSpaceKey == null) return;
-        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
-        if (imm == null) return;
-        // The "..." popup hint for triggering something by a long-pressing the spacebar
-        final boolean shouldShowInputMethodPicker = mIsSpacebarTriggeringPopupByLongPress
-                && Utils.hasMultipleEnabledIMEsOrSubtypes(imm, true /* include aux subtypes */);
-        mSpaceKey.setNeedsSpecialPopupHint(shouldShowInputMethodPicker);
-        // If application locales are explicitly selected.
-        if (mSubtypeSwitcher.needsToDisplayLanguage(mId.mLocale)) {
-            mSpaceKey.setIcon(getSpaceDrawable(mId.mLocale, isAutoCorrection));
-        } else if (isAutoCorrection) {
-            mSpaceKey.setIcon(getSpaceDrawable(null, true));
-        } else {
-            mSpaceKey.setIcon(mSpaceIcon);
-        }
-    }
-
-    // Compute width of text with specified text size using paint.
-    private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) {
-        paint.setTextSize(textSize);
-        paint.getTextBounds(text, 0, text.length(), bounds);
-        return bounds.width();
-    }
-
-    // Layout local language name and left and right arrow on spacebar.
-    private static String layoutSpacebar(Paint paint, Locale locale, int width,
-            float origTextSize) {
-        final Rect bounds = new Rect();
-
-        // Estimate appropriate language name text size to fit in maxTextWidth.
-        String language = Utils.getFullDisplayName(locale, true);
-        int textWidth = getTextWidth(paint, language, origTextSize, bounds);
-        // Assuming text width and text size are proportional to each other.
-        float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
-        // allow variable text size
-        textWidth = getTextWidth(paint, language, textSize, bounds);
-        // If text size goes too small or text does not fit, use middle or short name
-        final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
-                || (textWidth > width);
-
-        final boolean useShortName;
-        if (useMiddleName) {
-            language = Utils.getMiddleDisplayLanguage(locale);
-            textWidth = getTextWidth(paint, language, origTextSize, bounds);
-            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
-            useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
-                    || (textWidth > width);
-        } else {
-            useShortName = false;
-        }
-
-        if (useShortName) {
-            language = Utils.getShortDisplayLanguage(locale);
-            textWidth = getTextWidth(paint, language, origTextSize, bounds);
-            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
-        }
-        paint.setTextSize(textSize);
-
-        return language;
-    }
-
-    private BitmapDrawable getSpaceDrawable(Locale locale, boolean isAutoCorrection) {
-        final Integer hashCode = Arrays.hashCode(
-                new Object[] { locale, isAutoCorrection, mSpacebarTextFadeFactor });
-        final BitmapDrawable cached = mSpaceDrawableCache.get(hashCode);
-        if (cached != null) {
-            return cached;
-        }
-        final BitmapDrawable drawable = new BitmapDrawable(mRes, drawSpacebar(
-                locale, isAutoCorrection, mSpacebarTextFadeFactor));
-        mSpaceDrawableCache.put(hashCode, drawable);
-        return drawable;
-    }
-
-    private Bitmap drawSpacebar(Locale inputLocale, boolean isAutoCorrection,
-            float textFadeFactor) {
-        final int width = mSpaceKey.mWidth;
-        final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight;
-        final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(buffer);
-        final Resources res = mRes;
-        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
-
-        // If application locales are explicitly selected.
-        if (inputLocale != null) {
-            final Paint paint = new Paint();
-            paint.setAntiAlias(true);
-            paint.setTextAlign(Align.CENTER);
-
-            final String textSizeOfLanguageOnSpacebar = res.getString(
-                    R.string.config_text_size_of_language_on_spacebar,
-                    SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR);
-            final int textStyle;
-            final int defaultTextSize;
-            if (MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR.equals(textSizeOfLanguageOnSpacebar)) {
-                textStyle = android.R.style.TextAppearance_Medium;
-                defaultTextSize = 18;
-            } else {
-                textStyle = android.R.style.TextAppearance_Small;
-                defaultTextSize = 14;
-            }
-
-            final String language = layoutSpacebar(paint, inputLocale, width, getTextSizeFromTheme(
-                    mTheme, textStyle, defaultTextSize));
-
-            // Draw language text with shadow
-            // In case there is no space icon, we will place the language text at the center of
-            // spacebar.
-            final float descent = paint.descent();
-            final float textHeight = -paint.ascent() + descent;
-            final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE
-                    : height / 2 + textHeight / 2;
-            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, textFadeFactor));
-            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
-            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, textFadeFactor));
-            canvas.drawText(language, width / 2, baseline - descent, paint);
-        }
-
-        // Draw the spacebar icon at the bottom
-        if (isAutoCorrection) {
-            final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
-            final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
-            int x = (width - iconWidth) / 2;
-            int y = height - iconHeight;
-            mAutoCorrectionSpacebarLedIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
-            mAutoCorrectionSpacebarLedIcon.draw(canvas);
-        } else if (mSpaceIcon != null) {
-            final int iconWidth = mSpaceIcon.getIntrinsicWidth();
-            final int iconHeight = mSpaceIcon.getIntrinsicHeight();
-            int x = (width - iconWidth) / 2;
-            int y = height - iconHeight;
-            mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
-            mSpaceIcon.draw(canvas);
-        }
-        return buffer;
-    }
-
-    @Override
-    public int[] getNearestKeys(int x, int y) {
-        // Avoid dead pixels at edges of the keyboard
-        return super.getNearestKeys(Math.max(0, Math.min(x, mOccupiedWidth - 1)),
-                Math.max(0, Math.min(y, mOccupiedHeight - 1)));
-    }
-
-    private static final int[] ATTR_TEXT_SIZE = { android.R.attr.textSize };
-
-    public static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
-        final TypedArray a = theme.obtainStyledAttributes(style, ATTR_TEXT_SIZE);
-        final int textSize = a.getDimensionPixelSize(a.getResourceId(0, 0), defValue);
-        a.recycle();
-        return textSize;
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 6ce3876..e2af971 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,33 +16,44 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
 import android.os.Message;
-import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.GestureDetector;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.PopupWindow;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
-import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResearchLogger;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeUtils;
 import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.define.ProductionFlag;
 
+import java.util.Locale;
 import java.util.WeakHashMap;
 
 /**
@@ -56,45 +67,72 @@
         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
     private static final String TAG = LatinKeyboardView.class.getSimpleName();
 
-    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
+    // TODO: Kill process when the usability study mode was changed.
+    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
 
-    private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
+    /** Listener for {@link KeyboardActionListener}. */
+    private KeyboardActionListener mKeyboardActionListener;
 
-    // Timing constants
-    private final int mKeyRepeatInterval;
+    /* Space key and its icons */
+    private Key mSpaceKey;
+    private Drawable mSpaceIcon;
+    // Stuff to draw language name on spacebar.
+    private final int mLanguageOnSpacebarFinalAlpha;
+    private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
+    private static final int ALPHA_OPAQUE = 255;
+    private boolean mNeedsToDisplayLanguage;
+    private Locale mSpacebarLocale;
+    private int mLanguageOnSpacebarAnimAlpha = ALPHA_OPAQUE;
+    private final float mSpacebarTextRatio;
+    private float mSpacebarTextSize;
+    private final int mSpacebarTextColor;
+    private final int mSpacebarTextShadowColor;
+    // If the full language name needs to be smaller than this value to be drawn on space key,
+    // its short language name will be used instead.
+    private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
+    // Stuff to draw auto correction LED on spacebar.
+    private boolean mAutoCorrectionSpacebarLedOn;
+    private final boolean mAutoCorrectionSpacebarLedEnabled;
+    private final Drawable mAutoCorrectionSpacebarLedIcon;
+    private static final int SPACE_LED_LENGTH_PERCENT = 80;
 
-    // Mini keyboard
+    // Stuff to draw altCodeWhileTyping keys.
+    private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
+    private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
+    private int mAltCodeKeyWhileTypingAnimAlpha = ALPHA_OPAQUE;
+
+    // More keys keyboard
     private PopupWindow mMoreKeysWindow;
     private MoreKeysPanel mMoreKeysPanel;
     private int mMoreKeysPanelPointerTrackerId;
     private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
             new WeakHashMap<Key, MoreKeysPanel>();
+    private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
 
-    /** Listener for {@link KeyboardActionListener}. */
-    private KeyboardActionListener mKeyboardActionListener;
+    private final PointerTrackerParams mPointerTrackerParams;
+    private final boolean mIsSpacebarTriggeringPopupByLongPress;
+    private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
 
-    private final boolean mHasDistinctMultitouch;
-    private int mOldPointerCount = 1;
-    private int mOldKeyIndex;
-
-    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
     protected KeyDetector mKeyDetector;
+    private boolean mHasDistinctMultitouch;
+    private int mOldPointerCount = 1;
+    private Key mOldKey;
 
-    // To detect double tap.
-    protected GestureDetector mGestureDetector;
-
-    private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this);
+    private final KeyTimerHandler mKeyTimerHandler;
 
     private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView>
             implements TimerProxy {
         private static final int MSG_REPEAT_KEY = 1;
         private static final int MSG_LONGPRESS_KEY = 2;
-        private static final int MSG_IGNORE_DOUBLE_TAP = 3;
+        private static final int MSG_DOUBLE_TAP = 3;
+        private static final int MSG_TYPING_STATE_EXPIRED = 4;
 
+        private final KeyTimerParams mParams;
         private boolean mInKeyRepeat;
 
-        public KeyTimerHandler(LatinKeyboardView outerInstance) {
+        public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) {
             super(outerInstance);
+            mParams = params;
         }
 
         @Override
@@ -103,19 +141,31 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_REPEAT_KEY:
-                tracker.onRepeatKey(msg.arg1);
-                startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker);
+                tracker.onRepeatKey(tracker.getKey());
+                startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
                 break;
             case MSG_LONGPRESS_KEY:
-                keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
+                if (tracker != null) {
+                    keyboardView.openMoreKeysKeyboardIfRequired(tracker.getKey(), tracker);
+                } else {
+                    KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1);
+                }
+                break;
+            case MSG_TYPING_STATE_EXPIRED:
+                cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
+                        keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
                 break;
             }
         }
 
+        private void startKeyRepeatTimer(PointerTracker tracker, long delay) {
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
+        }
+
         @Override
-        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
+        public void startKeyRepeatTimer(PointerTracker tracker) {
             mInKeyRepeat = true;
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
+            startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
         }
 
         public void cancelKeyRepeatTimer() {
@@ -128,9 +178,49 @@
         }
 
         @Override
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+        public void startLongPressTimer(int code) {
             cancelLongPressTimer();
-            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+            final int delay;
+            switch (code) {
+            case Keyboard.CODE_SHIFT:
+                delay = mParams.mLongPressShiftKeyTimeout;
+                break;
+            default:
+                delay = 0;
+                break;
+            }
+            if (delay > 0) {
+                sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay);
+            }
+        }
+
+        @Override
+        public void startLongPressTimer(PointerTracker tracker) {
+            cancelLongPressTimer();
+            if (tracker != null) {
+                final Key key = tracker.getKey();
+                final int delay;
+                switch (key.mCode) {
+                case Keyboard.CODE_SHIFT:
+                    delay = mParams.mLongPressShiftKeyTimeout;
+                    break;
+                case Keyboard.CODE_SPACE:
+                    delay = mParams.mLongPressSpaceKeyTimeout;
+                    break;
+                default:
+                    if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
+                        // We use longer timeout for sliding finger input started from the symbols
+                        // mode key.
+                        delay = mParams.mLongPressKeyTimeout * 3;
+                    } else {
+                        delay = mParams.mLongPressKeyTimeout;
+                    }
+                    break;
+                }
+                if (delay > 0) {
+                    sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
+                }
+            }
         }
 
         @Override
@@ -138,73 +228,118 @@
             removeMessages(MSG_LONGPRESS_KEY);
         }
 
+        public static void cancelAndStartAnimators(ObjectAnimator animatorToCancel,
+                ObjectAnimator animatorToStart) {
+            if (animatorToCancel != null && animatorToCancel.isStarted()) {
+                animatorToCancel.cancel();
+            }
+            // TODO: Start the animation with an initial value that is the same as the final value
+            // of the above animation when it gets cancelled.
+            if (animatorToStart != null && !animatorToStart.isStarted()) {
+                animatorToStart.start();
+            }
+        }
+
+        private void cancelTypingStateTimer() {
+            removeMessages(MSG_TYPING_STATE_EXPIRED);
+        }
+
+        @Override
+        public void startTypingStateTimer() {
+            final boolean isTyping = isTypingState();
+            cancelTypingStateTimer();
+            sendMessageDelayed(
+                    obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
+            if (isTyping) {
+                return;
+            }
+            final LatinKeyboardView keyboardView = getOuterInstance();
+            cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
+                    keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
+        }
+
+        @Override
+        public boolean isTypingState() {
+            return hasMessages(MSG_TYPING_STATE_EXPIRED);
+        }
+
+        @Override
+        public void startDoubleTapTimer() {
+            sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
+                    ViewConfiguration.getDoubleTapTimeout());
+        }
+
+        @Override
+        public void cancelDoubleTapTimer() {
+            removeMessages(MSG_DOUBLE_TAP);
+        }
+
+        @Override
+        public boolean isInDoubleTapTimeout() {
+            return hasMessages(MSG_DOUBLE_TAP);
+        }
+
         @Override
         public void cancelKeyTimers() {
             cancelKeyRepeatTimer();
             cancelLongPressTimer();
-            removeMessages(MSG_IGNORE_DOUBLE_TAP);
-        }
-
-        public void startIgnoringDoubleTap() {
-            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
-                    ViewConfiguration.getDoubleTapTimeout());
-        }
-
-        public boolean isIgnoringDoubleTap() {
-            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
         }
 
         public void cancelAllMessages() {
             cancelKeyTimers();
+            cancelTypingStateTimer();
         }
     }
 
-    private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
-        private boolean mProcessingShiftDoubleTapEvent = false;
+    public static class PointerTrackerParams {
+        public final boolean mSlidingKeyInputEnabled;
+        public final int mTouchNoiseThresholdTime;
+        public final float mTouchNoiseThresholdDistance;
 
-        @Override
-        public boolean onDoubleTap(MotionEvent firstDown) {
-            final Keyboard keyboard = getKeyboard();
-            if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard
-                    && ((LatinKeyboard) keyboard).isAlphaKeyboard()) {
-                final int pointerIndex = firstDown.getActionIndex();
-                final int id = firstDown.getPointerId(pointerIndex);
-                final PointerTracker tracker = getPointerTracker(id);
-                // If the first down event is on shift key.
-                if (tracker.isOnShiftKey((int) firstDown.getX(), (int) firstDown.getY())) {
-                    mProcessingShiftDoubleTapEvent = true;
-                    return true;
-                }
-            }
-            mProcessingShiftDoubleTapEvent = false;
-            return false;
+        public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
+
+        private PointerTrackerParams() {
+            mSlidingKeyInputEnabled = false;
+            mTouchNoiseThresholdTime =0;
+            mTouchNoiseThresholdDistance = 0;
         }
 
-        @Override
-        public boolean onDoubleTapEvent(MotionEvent secondTap) {
-            if (mProcessingShiftDoubleTapEvent
-                    && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
-                final MotionEvent secondDown = secondTap;
-                final int pointerIndex = secondDown.getActionIndex();
-                final int id = secondDown.getPointerId(pointerIndex);
-                final PointerTracker tracker = getPointerTracker(id);
-                // If the second down event is also on shift key.
-                if (tracker.isOnShiftKey((int) secondDown.getX(), (int) secondDown.getY())) {
-                    // Detected a double tap on shift key. If we are in the ignoring double tap
-                    // mode, it means we have already turned off caps lock in
-                    // {@link KeyboardSwitcher#onReleaseShift} .
-                    onDoubleTapShiftKey(tracker, mKeyTimerHandler.isIgnoringDoubleTap());
-                    return true;
-                }
-                // Otherwise these events should not be handled as double tap.
-                mProcessingShiftDoubleTapEvent = false;
-            }
-            return mProcessingShiftDoubleTapEvent;
+        public PointerTrackerParams(TypedArray latinKeyboardViewAttr) {
+            mSlidingKeyInputEnabled = latinKeyboardViewAttr.getBoolean(
+                    R.styleable.LatinKeyboardView_slidingKeyInputEnable, false);
+            mTouchNoiseThresholdTime = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_touchNoiseThresholdTime, 0);
+            mTouchNoiseThresholdDistance = latinKeyboardViewAttr.getDimension(
+                    R.styleable.LatinKeyboardView_touchNoiseThresholdDistance, 0);
+        }
+    }
+
+    static class KeyTimerParams {
+        public final int mKeyRepeatStartTimeout;
+        public final int mKeyRepeatInterval;
+        public final int mLongPressKeyTimeout;
+        public final int mLongPressShiftKeyTimeout;
+        public final int mLongPressSpaceKeyTimeout;
+        public final int mIgnoreAltCodeKeyTimeout;
+
+        public KeyTimerParams(TypedArray latinKeyboardViewAttr) {
+            mKeyRepeatStartTimeout = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_keyRepeatStartTimeout, 0);
+            mKeyRepeatInterval = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_keyRepeatInterval, 0);
+            mLongPressKeyTimeout = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_longPressKeyTimeout, 0);
+            mLongPressShiftKeyTimeout = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_longPressShiftKeyTimeout, 0);
+            mLongPressSpaceKeyTimeout = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_longPressSpaceKeyTimeout, 0);
+            mIgnoreAltCodeKeyTimeout = latinKeyboardViewAttr.getInt(
+                    R.styleable.LatinKeyboardView_ignoreAltCodeKeyTimeout, 0);
         }
     }
 
     public LatinKeyboardView(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.keyboardViewStyle);
+        this(context, attrs, R.attr.latinKeyboardViewStyle);
     }
 
     public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
@@ -212,27 +347,80 @@
 
         mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
 
-        final Resources res = getResources();
-        mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
-                R.bool.config_show_mini_keyboard_at_touched_point);
-        final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
-        mKeyDetector = new KeyDetector(keyHysteresisDistance);
-
-        final boolean ignoreMultitouch = true;
-        mGestureDetector = new GestureDetector(
-                getContext(), new DoubleTapListener(), null, ignoreMultitouch);
-        mGestureDetector.setIsLongpressEnabled(false);
-
         mHasDistinctMultitouch = context.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
 
-        PointerTracker.init(mHasDistinctMultitouch, getContext());
+        PointerTracker.init(mHasDistinctMultitouch);
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.LatinKeyboardView, defStyle, R.style.LatinKeyboardView);
+        mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
+                R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedEnabled, false);
+        mAutoCorrectionSpacebarLedIcon = a.getDrawable(
+                R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedIcon);
+        mSpacebarTextRatio = a.getFraction(R.styleable.LatinKeyboardView_spacebarTextRatio,
+                1000, 1000, 1) / 1000.0f;
+        mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0);
+        mSpacebarTextShadowColor = a.getColor(
+                R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0);
+        mLanguageOnSpacebarFinalAlpha = a.getInt(
+                R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, ALPHA_OPAQUE);
+        final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
+                R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
+        final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
+                R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
+        final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
+                R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
+
+        final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
+        mPointerTrackerParams = new PointerTrackerParams(a);
+        mIsSpacebarTriggeringPopupByLongPress = (keyTimerParams.mLongPressSpaceKeyTimeout > 0);
+
+        final float keyHysteresisDistance = a.getDimension(
+                R.styleable.LatinKeyboardView_keyHysteresisDistance, 0);
+        mKeyDetector = new KeyDetector(keyHysteresisDistance);
+        mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams);
+        mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
+                R.styleable.LatinKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
+        a.recycle();
+
+        PointerTracker.setParameters(mPointerTrackerParams);
+
+        mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
+                languageOnSpacebarFadeoutAnimatorResId, this);
+        mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
+                altCodeKeyWhileTypingFadeoutAnimatorResId, this);
+        mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
+                altCodeKeyWhileTypingFadeinAnimatorResId, this);
     }
 
-    public void startIgnoringDoubleTap() {
-        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
-            mKeyTimerHandler.startIgnoringDoubleTap();
+    private ObjectAnimator loadObjectAnimator(int resId, Object target) {
+        if (resId == 0) return null;
+        final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
+                getContext(), resId);
+        if (animator != null) {
+            animator.setTarget(target);
+        }
+        return animator;
+    }
+
+    // Getter/setter methods for {@link ObjectAnimator}.
+    public int getLanguageOnSpacebarAnimAlpha() {
+        return mLanguageOnSpacebarAnimAlpha;
+    }
+
+    public void setLanguageOnSpacebarAnimAlpha(int alpha) {
+        mLanguageOnSpacebarAnimAlpha = alpha;
+        invalidateKey(mSpaceKey);
+    }
+
+    public int getAltCodeKeyWhileTypingAnimAlpha() {
+        return mAltCodeKeyWhileTypingAnimAlpha;
+    }
+
+    public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) {
+        mAltCodeKeyWhileTypingAnimAlpha = alpha;
+        updateAltCodeKeyWhileTyping();
     }
 
     public void setKeyboardActionListener(KeyboardActionListener listener) {
@@ -264,20 +452,6 @@
         return mKeyTimerHandler;
     }
 
-    @Override
-    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
-        final Keyboard keyboard = getKeyboard();
-        if (keyboard instanceof LatinKeyboard) {
-            final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
-            if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
-                // Phone and number keyboard never shows popup preview.
-                super.setKeyPreviewPopupEnabled(false, delay);
-                return;
-            }
-        }
-        super.setKeyPreviewPopupEnabled(previewEnabled, delay);
-    }
-
     /**
      * 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.
@@ -292,10 +466,15 @@
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
-        mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
         PointerTracker.setKeyDetector(mKeyDetector);
         mTouchScreenRegulator.setKeyboard(keyboard);
         mMoreKeysPanelCache.clear();
+
+        mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE);
+        mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon(keyboard.mIconsSet) : null;
+        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
+        mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
+        mSpacebarLocale = keyboard.mId.mLocale;
     }
 
     /**
@@ -306,6 +485,10 @@
         return mHasDistinctMultitouch;
     }
 
+    public void setDistinctMultitouch(boolean hasDistinctMultitouch) {
+        mHasDistinctMultitouch = hasDistinctMultitouch;
+    }
+
     /**
      * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
      * codes for adjacent keys.  When disabled, only the primary key code will be
@@ -329,7 +512,7 @@
         super.cancelAllMessages();
     }
 
-    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+    private boolean openMoreKeysKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
         // Check if we have a popup layout specified first.
         if (mMoreKeysLayout == 0) {
             return false;
@@ -338,22 +521,11 @@
         // Check if we are already displaying popup panel.
         if (mMoreKeysPanel != null)
             return false;
-        final Key parentKey = tracker.getKey(keyIndex);
         if (parentKey == null)
             return false;
         return onLongPress(parentKey, tracker);
     }
 
-    private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker,
-            final boolean ignore) {
-        // When shift key is double tapped, the first tap is correctly processed as usual tap. And
-        // the second tap is treated as this double tap event, so that we need not mark tracker
-        // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
-        final int primaryCode = ignore ? Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY
-                : Keyboard.CODE_CAPSLOCK;
-        invokeCodeInput(primaryCode);
-    }
-
     // This default implementation returns a more keys panel.
     protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) {
         if (parentKey.mMoreKeys == null)
@@ -363,28 +535,19 @@
         if (container == null)
             throw new NullPointerException();
 
-        final MiniKeyboardView miniKeyboardView =
-                (MiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
+        final MoreKeysKeyboardView moreKeysKeyboardView =
+                (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
         final Keyboard parentKeyboard = getKeyboard();
-        final Keyboard miniKeyboard = new MiniKeyboard.Builder(
+        final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(
                 this, parentKeyboard.mMoreKeysTemplate, parentKey, parentKeyboard).build();
-        miniKeyboardView.setKeyboard(miniKeyboard);
+        moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 
-        return miniKeyboardView;
-    }
-
-    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
-        final Keyboard keyboard = getKeyboard();
-        // We should not set text fade factor to the keyboard which does not display the language on
-        // its spacebar.
-        if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
-            ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
-        }
+        return moreKeysKeyboardView;
     }
 
     /**
-     * Called when a key is long pressed. By default this will open mini keyboard associated
+     * Called when a key is long pressed. By default this will open more keys keyboard associated
      * with this key.
      * @param parentKey the key that was long pressed
      * @param tracker the pointer tracker which pressed the parent key
@@ -393,49 +556,37 @@
      */
     protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
         final int primaryCode = parentKey.mCode;
-        final Keyboard keyboard = getKeyboard();
-        if (keyboard instanceof LatinKeyboard) {
-            final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
-            if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
-                tracker.onLongPressed();
-                // Long pressing on 0 in phone number keypad gives you a '+'.
-                invokeCodeInput(Keyboard.CODE_PLUS);
-                invokeReleaseKey(primaryCode);
-                return true;
-            }
-            if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) {
-                tracker.onLongPressed();
-                invokeCodeInput(Keyboard.CODE_CAPSLOCK);
-                invokeReleaseKey(primaryCode);
-                return true;
-            }
+        if (parentKey.hasEmbeddedMoreKey()) {
+            final int embeddedCode = KeySpecParser.getCode(getResources(), parentKey.mMoreKeys[0]);
+            tracker.onLongPressed();
+            invokeCodeInput(embeddedCode);
+            invokeReleaseKey(primaryCode);
+            KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode);
+            return true;
         }
-        if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
-            // Both long pressing settings key and space key invoke IME switcher dialog.
+        if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) {
+            // Long pressing the space key invokes IME switcher dialog.
             if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
                 tracker.onLongPressed();
                 invokeReleaseKey(primaryCode);
                 return true;
-            } else {
-                return openMoreKeysPanel(parentKey, tracker);
             }
-        } else {
-            return openMoreKeysPanel(parentKey, tracker);
         }
+        return openMoreKeysPanel(parentKey, tracker);
     }
 
     private boolean invokeCustomRequest(int code) {
-        return getKeyboardActionListener().onCustomRequest(code);
+        return mKeyboardActionListener.onCustomRequest(code);
     }
 
     private void invokeCodeInput(int primaryCode) {
-        getKeyboardActionListener().onCodeInput(primaryCode, null,
+        mKeyboardActionListener.onCodeInput(primaryCode,
                 KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
                 KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
     }
 
     private void invokeReleaseKey(int primaryCode) {
-        getKeyboardActionListener().onRelease(primaryCode, false);
+        mKeyboardActionListener.onReleaseKey(primaryCode, false);
     }
 
     private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) {
@@ -449,30 +600,24 @@
         if (mMoreKeysWindow == null) {
             mMoreKeysWindow = new PopupWindow(getContext());
             mMoreKeysWindow.setBackgroundDrawable(null);
-            mMoreKeysWindow.setAnimationStyle(R.style.MiniKeyboardAnimation);
+            mMoreKeysWindow.setAnimationStyle(R.style.MoreKeysKeyboardAnimation);
         }
         mMoreKeysPanel = moreKeysPanel;
         mMoreKeysPanelPointerTrackerId = tracker.mPointerId;
 
         final Keyboard keyboard = getKeyboard();
-        moreKeysPanel.setShifted(keyboard.isShiftedOrShiftLocked());
-        final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
+        final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint) ? tracker.getLastX()
                 : parentKey.mX + parentKey.mWidth / 2;
         final int pointY = parentKey.mY - keyboard.mVerticalGap;
         moreKeysPanel.showMoreKeysPanel(
-                this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener());
+                this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
         final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
         final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
-        tracker.onShowMoreKeysPanel(
-                translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+        tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
         dimEntireKeyboard(true);
         return true;
     }
 
-    private PointerTracker getPointerTracker(final int id) {
-        return PointerTracker.getPointerTracker(id, this);
-    }
-
     public boolean isInSlidingKeyInput() {
         if (mMoreKeysPanel != null) {
             return true;
@@ -508,14 +653,6 @@
             return true;
         }
 
-        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
-        if (mMoreKeysPanel == null && mGestureDetector != null
-                && mGestureDetector.onTouchEvent(me)) {
-            PointerTracker.dismissAllKeyPreviews();
-            mKeyTimerHandler.cancelKeyTimers();
-            return true;
-        }
-
         final long eventTime = me.getEventTime();
         final int index = me.getActionIndex();
         final int id = me.getPointerId(index);
@@ -527,9 +664,52 @@
             x = (int)me.getX(index);
             y = (int)me.getY(index);
         }
+        if (ENABLE_USABILITY_STUDY_LOG) {
+            final String eventTag;
+            switch (action) {
+                case MotionEvent.ACTION_UP:
+                    eventTag = "[Up]";
+                    break;
+                case MotionEvent.ACTION_DOWN:
+                    eventTag = "[Down]";
+                    break;
+                case MotionEvent.ACTION_POINTER_UP:
+                    eventTag = "[PointerUp]";
+                    break;
+                case MotionEvent.ACTION_POINTER_DOWN:
+                    eventTag = "[PointerDown]";
+                    break;
+                case MotionEvent.ACTION_MOVE: // Skip this as being logged below
+                    eventTag = "";
+                    break;
+                default:
+                    eventTag = "[Action" + action + "]";
+                    break;
+            }
+            if (!TextUtils.isEmpty(eventTag)) {
+                final float size = me.getSize(index);
+                final float pressure = me.getPressure(index);
+                UsabilityStudyLogUtils.getInstance().write(
+                        eventTag + eventTime + "," + id + "," + x + "," + y + ","
+                        + size + "," + pressure);
+            }
+        }
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            if (ResearchLogger.sIsLogging) {
+                // TODO: remove redundant calculations of size and pressure by
+                // removing UsabilityStudyLog code once the ResearchLogger is mature enough
+                final float size = me.getSize(index);
+                final float pressure = me.getPressure(index);
+                if (action != MotionEvent.ACTION_MOVE) {
+                    // Skip ACTION_MOVE events as they are logged below
+                    ResearchLogger.getInstance().logMotionEvent(action, eventTime, id, x, y,
+                            size, pressure);
+                }
+            }
+        }
 
         if (mKeyTimerHandler.isInKeyRepeat()) {
-            final PointerTracker tracker = getPointerTracker(id);
+            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
             // Key repeating timer will be canceled if 2 or more keys are in action, and current
             // event (UP or DOWN) is non-modifier key.
             if (pointerCount > 1 && !tracker.isModifier()) {
@@ -543,13 +723,13 @@
         // multi-touch panel.
         if (nonDistinctMultitouch) {
             // Use only main (id=0) pointer tracker.
-            PointerTracker tracker = getPointerTracker(0);
+            final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
             if (pointerCount == 1 && oldPointerCount == 2) {
                 // Multi-touch to single touch transition.
                 // Send a down event for the latest pointer if the key is different from the
                 // previous key.
-                final int newKeyIndex = tracker.getKeyIndexOn(x, y);
-                if (mOldKeyIndex != newKeyIndex) {
+                final Key newKey = tracker.getKeyOn(x, y);
+                if (mOldKey != newKey) {
                     tracker.onDownEvent(x, y, eventTime, this);
                     if (action == MotionEvent.ACTION_UP)
                         tracker.onUpEvent(x, y, eventTime);
@@ -559,7 +739,7 @@
                 // Send an up event for the last pointer.
                 final int lastX = tracker.getLastX();
                 final int lastY = tracker.getLastY();
-                mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
+                mOldKey = tracker.getKeyOn(lastX, lastY);
                 tracker.onUpEvent(lastX, lastY, eventTime);
             } else if (pointerCount == 1 && oldPointerCount == 1) {
                 tracker.processMotionEvent(action, x, y, eventTime, this);
@@ -572,7 +752,9 @@
 
         if (action == MotionEvent.ACTION_MOVE) {
             for (int i = 0; i < pointerCount; i++) {
-                final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
+                final int pointerId = me.getPointerId(i);
+                final PointerTracker tracker = PointerTracker.getPointerTracker(
+                        pointerId, this);
                 final int px, py;
                 if (mMoreKeysPanel != null
                         && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
@@ -583,9 +765,26 @@
                     py = (int)me.getY(i);
                 }
                 tracker.onMoveEvent(px, py, eventTime);
+                if (ENABLE_USABILITY_STUDY_LOG) {
+                    final float pointerSize = me.getSize(i);
+                    final float pointerPressure = me.getPressure(i);
+                    UsabilityStudyLogUtils.getInstance().write("[Move]"  + eventTime + ","
+                            + pointerId + "," + px + "," + py + ","
+                            + pointerSize + "," + pointerPressure);
+                }
+                if (ProductionFlag.IS_EXPERIMENTAL) {
+                    if (ResearchLogger.sIsLogging) {
+                        // TODO: earlier comment about redundant calculations applies here too
+                        final float pointerSize = me.getSize(i);
+                        final float pointerPressure = me.getPressure(i);
+                        ResearchLogger.getInstance().logMotionEvent(action, eventTime, pointerId,
+                                px, py, pointerSize, pointerPressure);
+                    }
+                }
             }
         } else {
-            getPointerTracker(id).processMotionEvent(action, x, y, eventTime, this);
+            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
+            tracker.processMotionEvent(action, x, y, eventTime, this);
         }
 
         return true;
@@ -623,44 +822,176 @@
                 super.draw(c);
                 tryGC = false;
             } catch (OutOfMemoryError e) {
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
+                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e);
             }
         }
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        // Token is available from here.
-        VoiceProxy.getInstance().onAttachedToWindow();
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
-            return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
-                    event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
-        }
-
-        return super.dispatchPopulateAccessibilityEvent(event);
-    }
-
     /**
-     * Receives hover events from the input framework. This method overrides
-     * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On
-     * lower SDK versions, this method is never called.
+     * Receives hover events from the input framework.
      *
      * @param event The motion event to be dispatched.
      * @return {@code true} if the event was handled by the view, {@code false}
      *         otherwise
      */
+    @Override
     public boolean dispatchHoverEvent(MotionEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
+            final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
             return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
         }
 
         // Reflection doesn't support calling superclass methods.
         return false;
     }
+
+    public void updateShortcutKey(boolean available) {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard == null) return;
+        final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT);
+        if (shortcutKey == null) return;
+        shortcutKey.setEnabled(available);
+        invalidateKey(shortcutKey);
+    }
+
+    private void updateAltCodeKeyWhileTyping() {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard == null) return;
+        for (final Key key : keyboard.mAltCodeKeysWhileTyping) {
+            invalidateKey(key);
+        }
+    }
+
+    public void startDisplayLanguageOnSpacebar(boolean subtypeChanged,
+            boolean needsToDisplayLanguage) {
+        final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
+        mNeedsToDisplayLanguage = needsToDisplayLanguage;
+        if (animator == null) {
+            mNeedsToDisplayLanguage = false;
+        } else {
+            if (subtypeChanged && needsToDisplayLanguage) {
+                setLanguageOnSpacebarAnimAlpha(ALPHA_OPAQUE);
+                if (animator.isStarted()) {
+                    animator.cancel();
+                }
+                animator.start();
+            } else {
+                if (!animator.isStarted()) {
+                    mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
+                }
+            }
+        }
+        invalidateKey(mSpaceKey);
+    }
+
+    public void updateAutoCorrectionState(boolean isAutoCorrection) {
+        if (!mAutoCorrectionSpacebarLedEnabled) return;
+        mAutoCorrectionSpacebarLedOn = isAutoCorrection;
+        invalidateKey(mSpaceKey);
+    }
+
+    @Override
+    protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
+        if (key.altCodeWhileTyping() && key.isEnabled()) {
+            params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
+        }
+        if (key.mCode == Keyboard.CODE_SPACE) {
+            drawSpacebar(key, canvas, paint);
+
+            // Whether space key needs to show the "..." popup hint for special purposes
+            if (mIsSpacebarTriggeringPopupByLongPress
+                    && SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(
+                            true /* include aux subtypes */)) {
+                drawKeyPopupHint(key, canvas, paint, params);
+            }
+        } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) {
+            super.onDrawKeyTopVisuals(key, canvas, paint, params);
+            if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+                drawKeyPopupHint(key, canvas, paint, params);
+            }
+        } else {
+            super.onDrawKeyTopVisuals(key, canvas, paint, params);
+        }
+    }
+
+    // Compute width of text with specified text size using paint.
+    private int getTextWidth(Paint paint, String text, float textSize) {
+        paint.setTextSize(textSize);
+        return (int)getLabelWidth(text, paint);
+    }
+
+    // Layout locale language name on spacebar.
+    private String layoutLanguageOnSpacebar(Paint paint, Locale locale, int width,
+            float origTextSize) {
+        paint.setTextAlign(Align.CENTER);
+        paint.setTypeface(Typeface.DEFAULT);
+        // Estimate appropriate language name text size to fit in maxTextWidth.
+        String language = StringUtils.getFullDisplayName(locale, true);
+        int textWidth = getTextWidth(paint, language, origTextSize);
+        // Assuming text width and text size are proportional to each other.
+        float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
+        // allow variable text size
+        textWidth = getTextWidth(paint, language, textSize);
+        // If text size goes too small or text does not fit, use middle or short name
+        final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
+                || (textWidth > width);
+
+        final boolean useShortName;
+        if (useMiddleName) {
+            language = StringUtils.getMiddleDisplayLanguage(locale);
+            textWidth = getTextWidth(paint, language, origTextSize);
+            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
+            useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
+                    || (textWidth > width);
+        } else {
+            useShortName = false;
+        }
+
+        if (useShortName) {
+            language = StringUtils.getShortDisplayLanguage(locale);
+            textWidth = getTextWidth(paint, language, origTextSize);
+            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
+        }
+        paint.setTextSize(textSize);
+
+        return language;
+    }
+
+    private void drawSpacebar(Key key, Canvas canvas, Paint paint) {
+        final int width = key.mWidth;
+        final int height = key.mHeight;
+
+        // If input subtypes are explicitly selected.
+        if (mNeedsToDisplayLanguage) {
+            final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width,
+                    mSpacebarTextSize);
+            // Draw language text with shadow
+            // In case there is no space icon, we will place the language text at the center of
+            // spacebar.
+            final float descent = paint.descent();
+            final float textHeight = -paint.ascent() + descent;
+            final float baseline = height / 2 + textHeight / 2;
+            paint.setColor(mSpacebarTextShadowColor);
+            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
+            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
+            paint.setColor(mSpacebarTextColor);
+            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
+            canvas.drawText(language, width / 2, baseline - descent, paint);
+        }
+
+        // Draw the spacebar icon at the bottom
+        if (mAutoCorrectionSpacebarLedOn) {
+            final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
+            final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
+            int x = (width - iconWidth) / 2;
+            int y = height - iconHeight;
+            drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
+        } else if (mSpaceIcon != null) {
+            final int iconWidth = mSpaceIcon.getIntrinsicWidth();
+            final int iconHeight = mSpaceIcon.getIntrinsicHeight();
+            int x = (width - iconWidth) / 2;
+            int y = height - iconHeight;
+            drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
deleted file mode 100644
index ac9290b..0000000
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import android.graphics.Paint;
-
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
-import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.keyboard.internal.MoreKeySpecParser;
-import com.android.inputmethod.latin.R;
-
-public class MiniKeyboard extends Keyboard {
-    private final int mDefaultKeyCoordX;
-
-    private MiniKeyboard(Builder.MiniKeyboardParams params) {
-        super(params);
-        mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
-    }
-
-    public int getDefaultCoordX() {
-        return mDefaultKeyCoordX;
-    }
-
-    public static class Builder extends KeyboardBuilder<Builder.MiniKeyboardParams> {
-        private final CharSequence[] mMoreKeys;
-
-        public static class MiniKeyboardParams extends KeyboardParams {
-            /* package */int mTopRowAdjustment;
-            public int mNumRows;
-            public int mNumColumns;
-            public int mLeftKeys;
-            public int mRightKeys; // includes default key.
-
-            public MiniKeyboardParams() {
-                super();
-            }
-
-            /* package for test */MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth,
-                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
-                super();
-                setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent,
-                        parentKeyboardWidth);
-            }
-
-            /**
-             * Set keyboard parameters of mini keyboard.
-             *
-             * @param numKeys number of keys in this mini keyboard.
-             * @param maxColumns number of maximum columns of this mini keyboard.
-             * @param keyWidth mini keyboard key width in pixel, including horizontal gap.
-             * @param rowHeight mini keyboard row height in pixel, including vertical gap.
-             * @param coordXInParent coordinate x of the popup key in parent keyboard.
-             * @param parentKeyboardWidth parent keyboard width in pixel.
-             */
-            public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
-                    int coordXInParent, int parentKeyboardWidth) {
-                if (parentKeyboardWidth / keyWidth < maxColumns) {
-                    throw new IllegalArgumentException(
-                            "Keyboard is too small to hold mini keyboard: " + parentKeyboardWidth
-                                    + " " + keyWidth + " " + maxColumns);
-                }
-                mDefaultKeyWidth = keyWidth;
-                mDefaultRowHeight = rowHeight;
-
-                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
-                mNumRows = numRows;
-                final int numColumns = getOptimizedColumns(numKeys, maxColumns);
-                mNumColumns = numColumns;
-
-                final int numLeftKeys = (numColumns - 1) / 2;
-                final int numRightKeys = numColumns - numLeftKeys; // including default key.
-                final int maxLeftKeys = coordXInParent / keyWidth;
-                final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent)
-                        / keyWidth);
-                int leftKeys, rightKeys;
-                if (numLeftKeys > maxLeftKeys) {
-                    leftKeys = maxLeftKeys;
-                    rightKeys = numColumns - maxLeftKeys;
-                } else if (numRightKeys > maxRightKeys) {
-                    leftKeys = numColumns - maxRightKeys;
-                    rightKeys = maxRightKeys;
-                } else {
-                    leftKeys = numLeftKeys;
-                    rightKeys = numRightKeys;
-                }
-                // Shift right if the left edge of mini keyboard is on the edge of parent keyboard
-                // unless the parent key is on the left edge.
-                if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) {
-                    leftKeys--;
-                    rightKeys++;
-                }
-                // Shift left if the right edge of mini keyboard is on the edge of parent keyboard
-                // unless the parent key is on the right edge.
-                if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) {
-                    leftKeys++;
-                    rightKeys--;
-                }
-                mLeftKeys = leftKeys;
-                mRightKeys = rightKeys;
-
-                // Centering of the top row.
-                final boolean onEdge = (leftKeys == 0 || rightKeys == 1);
-                if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) {
-                    mTopRowAdjustment = 0;
-                } else if (mLeftKeys < mRightKeys - 1) {
-                    mTopRowAdjustment = 1;
-                } else {
-                    mTopRowAdjustment = -1;
-                }
-
-                mBaseWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
-                // Need to subtract the bottom row's gutter only.
-                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
-                        + mTopPadding + mBottomPadding;
-            }
-
-            // Return key position according to column count (0 is default).
-            /* package */int getColumnPos(int n) {
-                final int col = n % mNumColumns;
-                if (col == 0) {
-                    // default position.
-                    return 0;
-                }
-                int pos = 0;
-                int right = 1; // include default position key.
-                int left = 0;
-                int i = 0;
-                while (true) {
-                    // Assign right key if available.
-                    if (right < mRightKeys) {
-                        pos = right;
-                        right++;
-                        i++;
-                    }
-                    if (i >= col)
-                        break;
-                    // Assign left key if available.
-                    if (left < mLeftKeys) {
-                        left++;
-                        pos = -left;
-                        i++;
-                    }
-                    if (i >= col)
-                        break;
-                }
-                return pos;
-            }
-
-            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
-                final int remainingKeys = numKeys % numColumns;
-                if (remainingKeys == 0) {
-                    return 0;
-                } else {
-                    return numColumns - remainingKeys;
-                }
-            }
-
-            private int getOptimizedColumns(int numKeys, int maxColumns) {
-                int numColumns = Math.min(numKeys, maxColumns);
-                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
-                    numColumns--;
-                }
-                return numColumns;
-            }
-
-            public int getDefaultKeyCoordX() {
-                return mLeftKeys * mDefaultKeyWidth;
-            }
-
-            public int getX(int n, int row) {
-                final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
-                if (isTopRow(row)) {
-                    return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
-                }
-                return x;
-            }
-
-            public int getY(int row) {
-                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
-            }
-
-            public void markAsEdgeKey(Key key, int row) {
-                if (row == 0)
-                    key.markAsTopEdge(this);
-                if (isTopRow(row))
-                    key.markAsBottomEdge(this);
-            }
-
-            private boolean isTopRow(int rowCount) {
-                return rowCount == mNumRows - 1;
-            }
-        }
-
-        public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
-            super(view.getContext(), new MiniKeyboardParams());
-            load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
-
-            // TODO: Mini keyboard's vertical gap is currently calculated heuristically.
-            // Should revise the algorithm.
-            mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
-            mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard;
-            mMoreKeys = parentKey.mMoreKeys;
-
-            final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
-            final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
-            final int width, height;
-            // Use pre-computed width and height if these values are available and mini keyboard
-            // has only one key to mitigate visual flicker between key preview and mini keyboard.
-            if (view.isKeyPreviewPopupEnabled() && mMoreKeys.length == 1 && previewWidth > 0
-                    && previewHeight > 0) {
-                width = previewWidth;
-                height = previewHeight + mParams.mVerticalGap;
-            } else {
-                width = getMaxKeyWidth(view, parentKey.mMoreKeys, mParams.mDefaultKeyWidth);
-                height = parentKeyboard.mMostCommonKeyHeight;
-            }
-            mParams.setParameters(mMoreKeys.length, parentKey.mMaxMoreKeysColumn, width, height,
-                    parentKey.mX + (mParams.mDefaultKeyWidth - width) / 2, view.getMeasuredWidth());
-        }
-
-        private static int getMaxKeyWidth(KeyboardView view, CharSequence[] moreKeys,
-                int minKeyWidth) {
-            final int padding = (int) view.getContext().getResources()
-                    .getDimension(R.dimen.mini_keyboard_key_horizontal_padding);
-            Paint paint = null;
-            int maxWidth = minKeyWidth;
-            for (CharSequence moreKeySpec : moreKeys) {
-                final CharSequence label = MoreKeySpecParser.getLabel(moreKeySpec.toString());
-                // If the label is single letter, minKeyWidth is enough to hold
-                // the label.
-                if (label != null && label.length() > 1) {
-                    if (paint == null) {
-                        paint = new Paint();
-                        paint.setAntiAlias(true);
-                    }
-                    final int width = (int)view.getDefaultLabelWidth(label, paint) + padding;
-                    if (maxWidth < width) {
-                        maxWidth = width;
-                    }
-                }
-            }
-            return maxWidth;
-        }
-
-        @Override
-        public MiniKeyboard build() {
-            final MiniKeyboardParams params = mParams;
-            for (int n = 0; n < mMoreKeys.length; n++) {
-                final String moreKeySpec = mMoreKeys[n].toString();
-                final int row = n / params.mNumColumns;
-                final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row),
-                        params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight);
-                params.markAsEdgeKey(key, row);
-                params.onAddKey(key);
-            }
-            return new MiniKeyboard(params);
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index d202046..cd4e300 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.keyboard;
 
-import java.util.List;
-
 public class MoreKeysDetector extends KeyDetector {
     private final int mSlideAllowanceSquare;
     private final int mSlideAllowanceSquareTop;
@@ -35,30 +33,19 @@
     }
 
     @Override
-    protected int getMaxNearbyKeys() {
-        // No nearby key will be returned.
-        return 1;
-    }
-
-    @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    public Key detectHitKey(int x, int y) {
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
-        int nearestIndex = NOT_A_KEY;
+        Key nearestKey = null;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        final int keyCount = keys.size();
-        for (int index = 0; index < keyCount; index++) {
-            final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
+        for (final Key key : getKeyboard().mKeys) {
+            final int dist = key.squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
-                nearestIndex = index;
+                nearestKey = key;
                 nearestDist = dist;
             }
         }
-
-        if (allCodes != null && nearestIndex != NOT_A_KEY)
-            allCodes[0] = keys.get(nearestIndex).mCode;
-        return nearestIndex;
+        return nearestKey;
     }
-}
\ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
new file mode 100644
index 0000000..72a5d0f
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+
+import com.android.inputmethod.keyboard.internal.KeySpecParser;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
+
+public class MoreKeysKeyboard extends Keyboard {
+    private final int mDefaultKeyCoordX;
+
+    MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
+        super(params);
+        mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
+    }
+
+    public int getDefaultCoordX() {
+        return mDefaultKeyCoordX;
+    }
+
+    public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
+        private final Key mParentKey;
+        private final Drawable mDivider;
+
+        private static final float LABEL_PADDING_RATIO = 0.2f;
+        private static final float DIVIDER_RATIO = 0.2f;
+
+        public static class MoreKeysKeyboardParams extends Keyboard.Params {
+            public boolean mIsFixedOrder;
+            /* package */int mTopRowAdjustment;
+            public int mNumRows;
+            public int mNumColumns;
+            public int mTopKeys;
+            public int mLeftKeys;
+            public int mRightKeys; // includes default key.
+            public int mDividerWidth;
+            public int mColumnWidth;
+
+            public MoreKeysKeyboardParams() {
+                super();
+            }
+
+            /**
+             * Set keyboard parameters of more keys keyboard.
+             *
+             * @param numKeys number of keys in this more keys keyboard.
+             * @param maxColumns number of maximum columns of this more keys keyboard.
+             * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
+             * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
+             * @param coordXInParent coordinate x of the key preview in parent keyboard.
+             * @param parentKeyboardWidth parent keyboard width in pixel.
+             * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
+             * @param dividerWidth width of divider, zero for no dividers.
+             */
+            public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
+                    int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder,
+                    int dividerWidth) {
+                mIsFixedOrder = isFixedColumnOrder;
+                if (parentKeyboardWidth / keyWidth < maxColumns) {
+                    throw new IllegalArgumentException(
+                            "Keyboard is too small to hold more keys keyboard: "
+                                    + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
+                }
+                mDefaultKeyWidth = keyWidth;
+                mDefaultRowHeight = rowHeight;
+
+                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+                mNumRows = numRows;
+                final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
+                        : getOptimizedColumns(numKeys, maxColumns);
+                mNumColumns = numColumns;
+                final int topKeys = numKeys % numColumns;
+                mTopKeys = topKeys == 0 ? numColumns : topKeys;
+
+                final int numLeftKeys = (numColumns - 1) / 2;
+                final int numRightKeys = numColumns - numLeftKeys; // including default key.
+                // Maximum number of keys we can layout both side of the parent key
+                final int maxLeftKeys = coordXInParent / keyWidth;
+                final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
+                int leftKeys, rightKeys;
+                if (numLeftKeys > maxLeftKeys) {
+                    leftKeys = maxLeftKeys;
+                    rightKeys = numColumns - leftKeys;
+                } else if (numRightKeys > maxRightKeys + 1) {
+                    rightKeys = maxRightKeys + 1; // include default key
+                    leftKeys = numColumns - rightKeys;
+                } else {
+                    leftKeys = numLeftKeys;
+                    rightKeys = numRightKeys;
+                }
+                // If the left keys fill the left side of the parent key, entire more keys keyboard
+                // should be shifted to the right unless the parent key is on the left edge.
+                if (maxLeftKeys == leftKeys && leftKeys > 0) {
+                    leftKeys--;
+                    rightKeys++;
+                }
+                // If the right keys fill the right side of the parent key, entire more keys
+                // should be shifted to the left unless the parent key is on the right edge.
+                if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
+                    leftKeys++;
+                    rightKeys--;
+                }
+                mLeftKeys = leftKeys;
+                mRightKeys = rightKeys;
+
+                // Adjustment of the top row.
+                mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
+                        : getAutoOrderTopRowAdjustment();
+                mDividerWidth = dividerWidth;
+                mColumnWidth = mDefaultKeyWidth + mDividerWidth;
+                mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
+                // Need to subtract the bottom row's gutter only.
+                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+                        + mTopPadding + mBottomPadding;
+            }
+
+            private int getFixedOrderTopRowAdjustment() {
+                if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
+                        || mLeftKeys == 0  || mRightKeys == 1) {
+                    return 0;
+                }
+                return -1;
+            }
+
+            private int getAutoOrderTopRowAdjustment() {
+                if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
+                        || mLeftKeys == 0 || mRightKeys == 1) {
+                    return 0;
+                }
+                return -1;
+            }
+
+            // Return key position according to column count (0 is default).
+            /* package */int getColumnPos(int n) {
+                return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
+            }
+
+            private int getFixedOrderColumnPos(int n) {
+                final int col = n % mNumColumns;
+                final int row = n / mNumColumns;
+                if (!isTopRow(row)) {
+                    return col - mLeftKeys;
+                }
+                final int rightSideKeys = mTopKeys / 2;
+                final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
+                final int pos = col - leftSideKeys;
+                final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
+                final int numRightKeys = mRightKeys - 1;
+                if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
+                    return pos;
+                } else if (numRightKeys < rightSideKeys) {
+                    return pos - (rightSideKeys - numRightKeys);
+                } else { // numLeftKeys < leftSideKeys
+                    return pos + (leftSideKeys - numLeftKeys);
+                }
+            }
+
+            private int getAutomaticColumnPos(int n) {
+                final int col = n % mNumColumns;
+                final int row = n / mNumColumns;
+                int leftKeys = mLeftKeys;
+                if (isTopRow(row)) {
+                    leftKeys += mTopRowAdjustment;
+                }
+                if (col == 0) {
+                    // default position.
+                    return 0;
+                }
+
+                int pos = 0;
+                int right = 1; // include default position key.
+                int left = 0;
+                int i = 0;
+                while (true) {
+                    // Assign right key if available.
+                    if (right < mRightKeys) {
+                        pos = right;
+                        right++;
+                        i++;
+                    }
+                    if (i >= col)
+                        break;
+                    // Assign left key if available.
+                    if (left < leftKeys) {
+                        left++;
+                        pos = -left;
+                        i++;
+                    }
+                    if (i >= col)
+                        break;
+                }
+                return pos;
+            }
+
+            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
+                final int remainings = numKeys % numColumns;
+                return remainings == 0 ? 0 : numColumns - remainings;
+            }
+
+            private int getOptimizedColumns(int numKeys, int maxColumns) {
+                int numColumns = Math.min(numKeys, maxColumns);
+                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
+                    numColumns--;
+                }
+                return numColumns;
+            }
+
+            public int getDefaultKeyCoordX() {
+                return mLeftKeys * mColumnWidth;
+            }
+
+            public int getX(int n, int row) {
+                final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
+                if (isTopRow(row)) {
+                    return x + mTopRowAdjustment * (mColumnWidth / 2);
+                }
+                return x;
+            }
+
+            public int getY(int row) {
+                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
+            }
+
+            public void markAsEdgeKey(Key key, int row) {
+                if (row == 0)
+                    key.markAsTopEdge(this);
+                if (isTopRow(row))
+                    key.markAsBottomEdge(this);
+            }
+
+            private boolean isTopRow(int rowCount) {
+                return mNumRows > 1 && rowCount == mNumRows - 1;
+            }
+        }
+
+        public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
+            super(view.getContext(), new MoreKeysKeyboardParams());
+            load(xmlId, parentKeyboard.mId);
+
+            // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
+            // Should revise the algorithm.
+            mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
+            mParentKey = parentKey;
+
+            final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
+            final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
+            final int width, height;
+            // Use pre-computed width and height if these values are available and more keys
+            // keyboard has only one key to mitigate visual flicker between key preview and more
+            // keys keyboard.
+            final boolean validKeyPreview = view.isKeyPreviewPopupEnabled()
+                    && !parentKey.noKeyPreview() && (previewWidth > 0) && (previewHeight > 0);
+            final boolean singleMoreKeyWithPreview = validKeyPreview
+                    && parentKey.mMoreKeys.length == 1;
+            if (singleMoreKeyWithPreview) {
+                width = previewWidth;
+                height = previewHeight + mParams.mVerticalGap;
+            } else {
+                width = getMaxKeyWidth(view, parentKey, mParams.mDefaultKeyWidth);
+                height = parentKeyboard.mMostCommonKeyHeight;
+            }
+            final int dividerWidth;
+            if (parentKey.needsDividersInMoreKeys()) {
+                mDivider = mResources.getDrawable(R.drawable.more_keys_divider);
+                // TODO: Drawable itself should have an alpha value.
+                mDivider.setAlpha(128);
+                dividerWidth = (int)(width * DIVIDER_RATIO);
+            } else {
+                mDivider = null;
+                dividerWidth = 0;
+            }
+            mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
+                    width, height, parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth(),
+                    parentKey.isFixedColumnOrderMoreKeys(), dividerWidth);
+        }
+
+        private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) {
+            final int padding = (int)(view.getResources()
+                    .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
+                    + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
+            final Paint paint = view.newDefaultLabelPaint();
+            paint.setTextSize(parentKey.hasLabelsInMoreKeys()
+                    ? view.mKeyDrawParams.mKeyLabelSize
+                    : view.mKeyDrawParams.mKeyLetterSize);
+            int maxWidth = minKeyWidth;
+            for (String moreKeySpec : parentKey.mMoreKeys) {
+                final String label = KeySpecParser.getLabel(moreKeySpec);
+                // If the label is single letter, minKeyWidth is enough to hold the label.
+                if (label != null && StringUtils.codePointCount(label) > 1) {
+                    final int width = (int)view.getLabelWidth(label, paint) + padding;
+                    if (maxWidth < width) {
+                        maxWidth = width;
+                    }
+                }
+            }
+            return maxWidth;
+        }
+
+        private static class MoreKeyDivider extends Key.Spacer {
+            private final Drawable mIcon;
+
+            public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) {
+                super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
+                mIcon = icon;
+            }
+
+            @Override
+            public Drawable getIcon(KeyboardIconsSet iconSet) {
+                // KeyboardIconsSet is unused. Use the icon that has been passed to the constructor.
+                return mIcon;
+            }
+        }
+
+        @Override
+        public MoreKeysKeyboard build() {
+            final MoreKeysKeyboardParams params = mParams;
+            // moreKeyFlags == 0 means that the rendered text size will be determined by its
+            // label's code point count.
+            final int moreKeyFlags = mParentKey.hasLabelsInMoreKeys() ? 0
+                    : Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
+            final String[] moreKeys = mParentKey.mMoreKeys;
+            for (int n = 0; n < moreKeys.length; n++) {
+                final String moreKeySpec = moreKeys[n];
+                final int row = n / params.mNumColumns;
+                final int x = params.getX(n, row);
+                final int y = params.getY(row);
+                final Key key = new Key(mResources, params, moreKeySpec, x, y,
+                        params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags);
+                params.markAsEdgeKey(key, row);
+                params.onAddKey(key);
+
+                final int pos = params.getColumnPos(n);
+                // The "pos" value represents the offset from the default position. Negative means
+                // left of the default position.
+                if (params.mDividerWidth > 0 && pos != 0) {
+                    final int dividerX = (pos > 0) ? x - params.mDividerWidth
+                            : x + params.mDefaultKeyWidth;
+                    final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y);
+                    params.onAddKey(divider);
+                }
+            }
+            return new MoreKeysKeyboard(params);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
similarity index 65%
rename from java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
rename to java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index f2c5b7b..e60fc95 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -28,10 +28,10 @@
 import com.android.inputmethod.latin.R;
 
 /**
- * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting
- * key presses and touch movements.
+ * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
+ * detecting key presses and touch movements.
  */
-public class MiniKeyboardView extends KeyboardView implements MoreKeysPanel {
+public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
     private final int[] mCoordinates = new int[2];
 
     private final KeyDetector mKeyDetector;
@@ -43,11 +43,13 @@
 
     private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter();
 
-    private final KeyboardActionListener mMiniKeyboardListener =
+    private final KeyboardActionListener mMoreKeysKeyboardListener =
             new KeyboardActionListener.Adapter() {
         @Override
-        public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
-            mListener.onCodeInput(primaryCode, keyCodes, x, y);
+        public void onCodeInput(int primaryCode, int x, int y) {
+            // Because a more keys keyboard doesn't need proximity characters correction, we don't
+            // send touch event coordinates.
+            mListener.onCodeInput(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE);
         }
 
         @Override
@@ -61,25 +63,26 @@
         }
 
         @Override
-        public void onPress(int primaryCode, boolean withSliding) {
-            mListener.onPress(primaryCode, withSliding);
+        public void onPressKey(int primaryCode) {
+            mListener.onPressKey(primaryCode);
         }
+
         @Override
-        public void onRelease(int primaryCode, boolean withSliding) {
-            mListener.onRelease(primaryCode, withSliding);
+        public void onReleaseKey(int primaryCode, boolean withSliding) {
+            mListener.onReleaseKey(primaryCode, withSliding);
         }
     };
 
-    public MiniKeyboardView(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.miniKeyboardViewStyle);
+    public MoreKeysKeyboardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
     }
 
-    public MiniKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+    public MoreKeysKeyboardView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
         final Resources res = context.getResources();
         mKeyDetector = new MoreKeysDetector(
-                res.getDimension(R.dimen.mini_keyboard_slide_allowance));
+                res.getDimension(R.dimen.more_keys_keyboard_slide_allowance));
         setKeyPreviewPopupEnabled(false, 0);
     }
 
@@ -109,7 +112,7 @@
 
     @Override
     public KeyboardActionListener getKeyboardActionListener() {
-        return mMiniKeyboardListener;
+        return mMoreKeysKeyboardListener;
     }
 
     @Override
@@ -124,53 +127,34 @@
 
     @Override
     public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
-        // Mini keyboard needs no pop-up key preview displayed, so we pass always false with a
+        // More keys keyboard needs no pop-up key preview displayed, so we pass always false with a
         // delay of 0. The delay does not matter actually since the popup is not shown anyway.
         super.setKeyPreviewPopupEnabled(false, 0);
     }
 
     @Override
-    public void setShifted(boolean shifted) {
-        final Keyboard keyboard = getKeyboard();
-        if (keyboard.setShifted(shifted)) {
-            invalidateAllKeys();
-        }
-    }
-
-    @Override
     public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY,
             PopupWindow window, KeyboardActionListener listener) {
         mController = controller;
         mListener = listener;
         final View container = (View)getParent();
-        final MiniKeyboard miniKeyboard = (MiniKeyboard)getKeyboard();
-
-        parentView.getLocationInWindow(mCoordinates);
-        final int miniKeyboardLeft = pointX - miniKeyboard.getDefaultCoordX()
+        final MoreKeysKeyboard pane = (MoreKeysKeyboard)getKeyboard();
+        final int defaultCoordX = pane.getDefaultCoordX();
+        // The coordinates of panel's left-top corner in parentView's coordinate system.
+        final int x = pointX - defaultCoordX - container.getPaddingLeft()
                 + parentView.getPaddingLeft();
-        final int x = wrapUp(Math.max(0, Math.min(miniKeyboardLeft,
-                parentView.getWidth() - miniKeyboard.mOccupiedWidth))
-                - container.getPaddingLeft() + mCoordinates[0],
-                container.getMeasuredWidth(), 0, parentView.getWidth());
-        final int y = pointY
-                - (container.getMeasuredHeight() - container.getPaddingBottom())
-                + parentView.getPaddingTop() + mCoordinates[1];
+        final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
+                + parentView.getPaddingTop();
 
         window.setContentView(container);
         window.setWidth(container.getMeasuredWidth());
         window.setHeight(container.getMeasuredHeight());
-        window.showAtLocation(parentView, Gravity.NO_GRAVITY, x, y);
+        parentView.getLocationInWindow(mCoordinates);
+        window.showAtLocation(parentView, Gravity.NO_GRAVITY,
+                x + mCoordinates[0], y + mCoordinates[1]);
 
-        mOriginX = x + container.getPaddingLeft() - mCoordinates[0];
-        mOriginY = y + container.getPaddingTop() - mCoordinates[1];
-    }
-
-    private static int wrapUp(int x, int width, int left, int right) {
-        if (x < left)
-            return left;
-        if (x + width > right)
-            return right - width;
-        return x;
+        mOriginX = x + container.getPaddingLeft();
+        mOriginY = y + container.getPaddingTop();
     }
 
     private boolean mIsDismissing;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
index 6314a99..f9a196d 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
@@ -24,8 +24,6 @@
         public boolean dismissMoreKeysPanel();
     }
 
-    public void setShifted(boolean shifted);
-
     /**
      * Show more keys panel.
      *
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 198e06a..ec90816 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,19 +16,15 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.content.Context;
-import android.content.res.Resources;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 
 public class PointerTracker {
     private static final String TAG = PointerTracker.class.getSimpleName();
@@ -67,40 +63,51 @@
     public interface DrawingProxy extends MoreKeysPanel.Controller {
         public void invalidateKey(Key key);
         public TextView inflateKeyPreviewText();
-        public void showKeyPreview(int keyIndex, PointerTracker tracker);
-        public void cancelShowKeyPreview(PointerTracker tracker);
+        public void showKeyPreview(PointerTracker tracker);
         public void dismissKeyPreview(PointerTracker tracker);
     }
 
     public interface TimerProxy {
-        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker);
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker);
+        public void startTypingStateTimer();
+        public boolean isTypingState();
+        public void startKeyRepeatTimer(PointerTracker tracker);
+        public void startLongPressTimer(PointerTracker tracker);
+        public void startLongPressTimer(int code);
         public void cancelLongPressTimer();
+        public void startDoubleTapTimer();
+        public void cancelDoubleTapTimer();
+        public boolean isInDoubleTapTimeout();
         public void cancelKeyTimers();
 
         public static class Adapter implements TimerProxy {
             @Override
-            public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {}
+            public void startTypingStateTimer() {}
             @Override
-            public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {}
+            public boolean isTypingState() { return false; }
+            @Override
+            public void startKeyRepeatTimer(PointerTracker tracker) {}
+            @Override
+            public void startLongPressTimer(PointerTracker tracker) {}
+            @Override
+            public void startLongPressTimer(int code) {}
             @Override
             public void cancelLongPressTimer() {}
             @Override
+            public void startDoubleTapTimer() {}
+            @Override
+            public void cancelDoubleTapTimer() {}
+            @Override
+            public boolean isInDoubleTapTimeout() { return false; }
+            @Override
             public void cancelKeyTimers() {}
         }
     }
 
-    private static KeyboardSwitcher sKeyboardSwitcher;
-    private static boolean sConfigSlidingKeyInputEnabled;
-    // Timing constants
-    private static int sDelayBeforeKeyRepeatStart;
-    private static int sLongPressKeyTimeout;
-    private static int sLongPressShiftKeyTimeout;
-    private static int sLongPressSpaceKeyTimeout;
-    private static int sTouchNoiseThresholdMillis;
+    // Parameters for pointer handling.
+    private static LatinKeyboardView.PointerTrackerParams sParams;
     private static int sTouchNoiseThresholdDistanceSquared;
 
-    private static final List<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
+    private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
     private static PointerTrackerQueue sPointerTrackerQueue;
 
     public final int mPointerId;
@@ -111,7 +118,6 @@
     private KeyboardActionListener mListener = EMPTY_LISTENER;
 
     private Keyboard mKeyboard;
-    private List<Key> mKeys;
     private int mKeyQuarterWidthSquared;
     private final TextView mKeyPreviewText;
 
@@ -119,9 +125,9 @@
     private long mDownTime;
     private long mUpTime;
 
-    // The current key index where this pointer is.
-    private int mKeyIndex = KeyDetector.NOT_A_KEY;
-    // The position where mKeyIndex was recognized for the first time.
+    // The current key where this pointer is.
+    private Key mCurrentKey = null;
+    // The position where the current key was recognized for the first time.
     private int mKeyX;
     private int mKeyY;
 
@@ -154,29 +160,24 @@
     private static final KeyboardActionListener EMPTY_LISTENER =
             new KeyboardActionListener.Adapter();
 
-    public static void init(boolean hasDistinctMultitouch, Context context) {
+    public static void init(boolean hasDistinctMultitouch) {
         if (hasDistinctMultitouch) {
             sPointerTrackerQueue = new PointerTrackerQueue();
         } else {
             sPointerTrackerQueue = null;
         }
 
-        final Resources res = context.getResources();
-        sConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
-        sDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
-        sLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
-        sLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout);
-        sLongPressSpaceKeyTimeout = res.getInteger(R.integer.config_long_press_space_key_timeout);
-        sTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis);
-        final float touchNoiseThresholdDistance = res.getDimension(
-                R.dimen.config_touch_noise_threshold_distance);
+        setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT);
+    }
+
+    public static void setParameters(LatinKeyboardView.PointerTrackerParams params) {
+        sParams = params;
         sTouchNoiseThresholdDistanceSquared = (int)(
-                touchNoiseThresholdDistance * touchNoiseThresholdDistance);
-        sKeyboardSwitcher = KeyboardSwitcher.getInstance();
+                params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
     }
 
     public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
-        final List<PointerTracker> trackers = sTrackers;
+        final ArrayList<PointerTracker> trackers = sTrackers;
 
         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
         for (int i = trackers.size(); i <= id; i++) {
@@ -207,7 +208,7 @@
 
     public static void dismissAllKeyPreviews() {
         for (final PointerTracker tracker : sTrackers) {
-            tracker.setReleasedKeyGraphics(tracker.mKeyIndex);
+            tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
         }
     }
 
@@ -227,15 +228,18 @@
     }
 
     // Returns true if keyboard has been changed by this callback.
-    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onPress    : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding
-                    + " ignoreModifier=" + ignoreModifierKey);
-        if (ignoreModifierKey)
+    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onPress    : " + KeyDetector.printableCode(key)
+                    + " ignoreModifier=" + ignoreModifierKey
+                    + " enabled=" + key.isEnabled());
+        }
+        if (ignoreModifierKey) {
             return false;
+        }
         if (key.isEnabled()) {
-            mListener.onPress(key.mCode, withSliding);
+            mListener.onPressKey(key.mCode);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
             mKeyboardLayoutHasBeenChanged = false;
             return keyboardLayoutHasBeenChanged;
@@ -245,36 +249,47 @@
 
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
-    private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
-                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
-                    + " ignoreModifier=" + ignoreModifierKey);
-        if (ignoreModifierKey)
+    private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) {
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+        final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
+        final int code = altersCode ? key.mAltCode : primaryCode;
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
+                    + " x=" + x + " y=" + y
+                    + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
+                    + " enabled=" + key.isEnabled());
+        }
+        if (ignoreModifierKey) {
             return;
-        if (key.isEnabled())
-            mListener.onCodeInput(primaryCode, keyCodes, x, y);
-    }
-
-    private void callListenerOnTextInput(Key key) {
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onTextInput: text=" + key.mOutputText);
-        if (key.isEnabled())
-            mListener.onTextInput(key.mOutputText);
+        }
+        // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
+        if (key.isEnabled() || altersCode) {
+            if (code == Keyboard.CODE_OUTPUT_TEXT) {
+                mListener.onTextInput(key.mOutputText);
+            } else if (code != Keyboard.CODE_UNSPECIFIED) {
+                mListener.onCodeInput(code, x, y);
+            }
+            if (!key.altCodeWhileTyping() && !key.isModifier()) {
+                mTimerProxy.startTypingStateTimer();
+            }
+        }
     }
 
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode) + " sliding="
-                    + withSliding + " ignoreModifier=" + ignoreModifierKey);
-        if (ignoreModifierKey)
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onRelease  : " + Keyboard.printableCode(primaryCode)
+                    + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
+                    + " enabled="+ key.isEnabled());
+        }
+        if (ignoreModifierKey) {
             return;
-        if (key.isEnabled())
-            mListener.onRelease(primaryCode, withSliding);
+        }
+        if (key.isEnabled()) {
+            mListener.onReleaseKey(primaryCode, withSliding);
+        }
     }
 
     private void callListenerOnCancelInput() {
@@ -286,7 +301,6 @@
     private void setKeyDetectorInner(KeyDetector keyDetector) {
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
-        mKeys = mKeyboard.mKeys;
         final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
     }
@@ -295,71 +309,96 @@
         return mIsInSlidingKeyInput;
     }
 
-    private boolean isValidKeyIndex(int keyIndex) {
-        return keyIndex >= 0 && keyIndex < mKeys.size();
-    }
-
-    public Key getKey(int keyIndex) {
-        return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
-    }
-
-    private static boolean isModifierCode(int primaryCode) {
-        return primaryCode == Keyboard.CODE_SHIFT
-                || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
-    }
-
-    private boolean isModifierInternal(int keyIndex) {
-        final Key key = getKey(keyIndex);
-        return key == null ? false : isModifierCode(key.mCode);
+    public Key getKey() {
+        return mCurrentKey;
     }
 
     public boolean isModifier() {
-        return isModifierInternal(mKeyIndex);
+        return mCurrentKey != null && mCurrentKey.isModifier();
     }
 
-    private boolean isOnModifierKey(int x, int y) {
-        return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
+    public Key getKeyOn(int x, int y) {
+        return mKeyDetector.detectHitKey(x, y);
     }
 
-    public boolean isOnShiftKey(int x, int y) {
-        final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
-        return key != null && key.mCode == Keyboard.CODE_SHIFT;
-    }
-
-    public int getKeyIndexOn(int x, int y) {
-        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
-    }
-
-    private void setReleasedKeyGraphics(int keyIndex) {
+    private void setReleasedKeyGraphics(Key key) {
         mDrawingProxy.dismissKeyPreview(this);
-        final Key key = getKey(keyIndex);
-        if (key != null && key.isEnabled()) {
-            key.onReleased();
-            mDrawingProxy.invalidateKey(key);
+        if (key == null) {
+            return;
         }
-    }
 
-    private void setPressedKeyGraphics(int keyIndex) {
-        final Key key = getKey(keyIndex);
-        if (key != null && key.isEnabled()) {
-            if (isKeyPreviewRequired(key)) {
-                mDrawingProxy.showKeyPreview(keyIndex, this);
+        // Even if the key is disabled, update the key release graphics just in case.
+        updateReleaseKeyGraphics(key);
+
+        if (key.isShift()) {
+            for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                if (shiftKey != key) {
+                    updateReleaseKeyGraphics(shiftKey);
+                }
             }
-            key.onPressed();
-            mDrawingProxy.invalidateKey(key);
+        }
+
+        if (key.altCodeWhileTyping()) {
+            final int altCode = key.mAltCode;
+            final Key altKey = mKeyboard.getKey(altCode);
+            if (altKey != null) {
+                updateReleaseKeyGraphics(altKey);
+            }
+            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
+                if (k != key && k.mAltCode == altCode) {
+                    updateReleaseKeyGraphics(k);
+                }
+            }
         }
     }
 
-    // The modifier key, such as shift key, should not show its key preview.
-    private static boolean isKeyPreviewRequired(Key key) {
-        final int code = key.mCode;
-        // TODO: Stop hard-coding these key codes here, and add a new key attribute of a key.
-        if (code == Keyboard.CODE_SPACE || code == Keyboard.CODE_ENTER
-                || code == Keyboard.CODE_DELETE || isModifierCode(code)
-                || code == Keyboard.CODE_SETTINGS || code == Keyboard.CODE_SHORTCUT) {
-            return false;
+    private void setPressedKeyGraphics(Key key) {
+        if (key == null) {
+            return;
         }
-        return true;
+
+        // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
+        final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
+        final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
+        if (!needsToUpdateGraphics) {
+            return;
+        }
+
+        if (!key.noKeyPreview()) {
+            mDrawingProxy.showKeyPreview(this);
+        }
+        updatePressKeyGraphics(key);
+
+        if (key.isShift()) {
+            for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                if (shiftKey != key) {
+                    updatePressKeyGraphics(shiftKey);
+                }
+            }
+        }
+
+        if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
+            final int altCode = key.mAltCode;
+            final Key altKey = mKeyboard.getKey(altCode);
+            if (altKey != null) {
+                updatePressKeyGraphics(altKey);
+            }
+            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
+                if (k != key && k.mAltCode == altCode) {
+                    updatePressKeyGraphics(k);
+                }
+            }
+        }
+    }
+
+    private void updateReleaseKeyGraphics(Key key) {
+        key.onReleased();
+        mDrawingProxy.invalidateKey(key);
+    }
+
+    private void updatePressKeyGraphics(Key key) {
+        key.onPressed();
+        mDrawingProxy.invalidateKey(key);
     }
 
     public int getLastX() {
@@ -374,31 +413,31 @@
         return mDownTime;
     }
 
-    private int onDownKey(int x, int y, long eventTime) {
+    private Key onDownKey(int x, int y, long eventTime) {
         mDownTime = eventTime;
         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
     }
 
-    private int onMoveKeyInternal(int x, int y) {
+    private Key onMoveKeyInternal(int x, int y) {
         mLastX = x;
         mLastY = y;
-        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+        return mKeyDetector.detectHitKey(x, y);
     }
 
-    private int onMoveKey(int x, int y) {
+    private Key onMoveKey(int x, int y) {
         return onMoveKeyInternal(x, y);
     }
 
-    private int onMoveToNewKey(int keyIndex, int x, int y) {
-        mKeyIndex = keyIndex;
+    private Key onMoveToNewKey(Key newKey, int x, int y) {
+        mCurrentKey = newKey;
         mKeyX = x;
         mKeyY = y;
-        return keyIndex;
+        return newKey;
     }
 
-    private int onUpKey(int x, int y, long eventTime) {
+    private Key onUpKey(int x, int y, long eventTime) {
         mUpTime = eventTime;
-        mKeyIndex = KeyDetector.NOT_A_KEY;
+        mCurrentKey = null;
         return onMoveKeyInternal(x, y);
     }
 
@@ -432,7 +471,7 @@
         setKeyDetectorInner(handler.getKeyDetector());
         // Naive up-to-down noise filter.
         final long deltaT = eventTime - mUpTime;
-        if (deltaT < sTouchNoiseThresholdMillis) {
+        if (deltaT < sParams.mTouchNoiseThresholdTime) {
             final int dx = x - mLastX;
             final int dy = y - mLastY;
             final int distanceSquared = (dx * dx + dy * dy);
@@ -447,7 +486,8 @@
 
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
-            if (isOnModifierKey(x, y)) {
+            final Key key = getKeyOn(x, y);
+            if (key != null && key.isModifier()) {
                 // Before processing a down event of modifier key, all pointers already being
                 // tracked should be released.
                 queue.releaseAllPointers(eventTime);
@@ -458,32 +498,35 @@
     }
 
     private void onDownEventInternal(int x, int y, long eventTime) {
-        int keyIndex = onDownKey(x, y, eventTime);
+        Key key = onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
         // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
-        mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
+        mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled
+                || (key != null && key.isModifier())
                 || mKeyDetector.alwaysAllowsSlidingInput();
         mKeyboardLayoutHasBeenChanged = false;
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
         mIsInSlidingKeyInput = false;
         mIgnoreModifierKey = false;
-        if (isValidKeyIndex(keyIndex)) {
+        if (key != null) {
             // This onPress call may have changed keyboard layout. Those cases are detected at
-            // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
+            // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
-                keyIndex = onDownKey(x, y, eventTime);
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+                key = onDownKey(x, y, eventTime);
+            }
 
-            startRepeatKey(keyIndex);
-            startLongPressTimer(keyIndex);
-            setPressedKeyGraphics(keyIndex);
+            startRepeatKey(key);
+            startLongPressTimer(key);
+            setPressedKeyGraphics(key);
         }
     }
 
     private void startSlidingKeyInput(Key key) {
-        if (!mIsInSlidingKeyInput)
-            mIgnoreModifierKey = isModifierCode(key.mCode);
+        if (!mIsInSlidingKeyInput) {
+            mIgnoreModifierKey = key.isModifier();
+        }
         mIsInSlidingKeyInput = true;
     }
 
@@ -495,39 +538,40 @@
 
         final int lastX = mLastX;
         final int lastY = mLastY;
-        final int oldKeyIndex = mKeyIndex;
-        final Key oldKey = getKey(oldKeyIndex);
-        int keyIndex = onMoveKey(x, y);
-        if (isValidKeyIndex(keyIndex)) {
+        final Key oldKey = mCurrentKey;
+        Key key = onMoveKey(x, y);
+        if (key != null) {
             if (oldKey == null) {
                 // The pointer has been slid in to the new key, but the finger was not on any keys.
                 // In this case, we must call onPress() to notify that the new key is being pressed.
                 // This onPress call may have changed keyboard layout. Those cases are detected at
-                // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
+                // {@link #setKeyboard}. In those cases, we should update key according to the
                 // new keyboard layout.
-                if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
-                    keyIndex = onMoveKey(x, y);
-                onMoveToNewKey(keyIndex, x, y);
-                startLongPressTimer(keyIndex);
-                setPressedKeyGraphics(keyIndex);
-            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
+                if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+                    key = onMoveKey(x, y);
+                }
+                onMoveToNewKey(key, x, y);
+                startLongPressTimer(key);
+                setPressedKeyGraphics(key);
+            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
                 // The pointer has been slid in to the new key from the previous key, we must call
                 // onRelease() first to notify that the previous key has been released, then call
                 // onPress() to notify that the new key is being pressed.
-                setReleasedKeyGraphics(oldKeyIndex);
+                setReleasedKeyGraphics(oldKey);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
                 mTimerProxy.cancelKeyTimers();
-                startRepeatKey(keyIndex);
+                startRepeatKey(key);
                 if (mIsAllowedSlidingKeyInput) {
                     // This onPress call may have changed keyboard layout. Those cases are detected
-                    // at {@link #setKeyboard}. In those cases, we should update keyIndex according
+                    // at {@link #setKeyboard}. In those cases, we should update key according
                     // to the new keyboard layout.
-                    if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
-                        keyIndex = onMoveKey(x, y);
-                    onMoveToNewKey(keyIndex, x, y);
-                    startLongPressTimer(keyIndex);
-                    setPressedKeyGraphics(keyIndex);
+                    if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+                        key = onMoveKey(x, y);
+                    }
+                    onMoveToNewKey(key, x, y);
+                    startLongPressTimer(key);
+                    setPressedKeyGraphics(key);
                 } else {
                     // HACK: On some devices, quick successive touches may be translated to sudden
                     // move by touch panel firmware. This hack detects the case and translates the
@@ -543,20 +587,20 @@
                         onDownEventInternal(x, y, eventTime);
                     } else {
                         mKeyAlreadyProcessed = true;
-                        setReleasedKeyGraphics(oldKeyIndex);
+                        setReleasedKeyGraphics(oldKey);
                     }
                 }
             }
         } else {
-            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
+            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
                 // The pointer has been slid out from the previous key, we must call onRelease() to
                 // notify that the previous key has been released.
-                setReleasedKeyGraphics(oldKeyIndex);
+                setReleasedKeyGraphics(oldKey);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
                 mTimerProxy.cancelLongPressTimer();
                 if (mIsAllowedSlidingKeyInput) {
-                    onMoveToNewKey(keyIndex, x, y);
+                    onMoveToNewKey(key, x, y);
                 } else {
                     mKeyAlreadyProcessed = true;
                 }
@@ -570,7 +614,7 @@
 
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
-            if (isModifier()) {
+            if (mCurrentKey != null && mCurrentKey.isModifier()) {
                 // Before processing an up event of modifier key, all pointers already being
                 // tracked should be released.
                 queue.releaseAllPointersExcept(this, eventTime);
@@ -594,7 +638,6 @@
 
     private void onUpEventInternal(int x, int y, long eventTime) {
         mTimerProxy.cancelKeyTimers();
-        mDrawingProxy.cancelShowKeyPreview(this);
         mIsInSlidingKeyInput = false;
         final int keyX, keyY;
         if (isMajorEnoughMoveToBeOnNewKey(x, y, onMoveKey(x, y))) {
@@ -605,8 +648,8 @@
             keyX = mKeyX;
             keyY = mKeyY;
         }
-        final int keyIndex = onUpKey(keyX, keyY, eventTime);
-        setReleasedKeyGraphics(keyIndex);
+        final Key key = onUpKey(keyX, keyY, eventTime);
+        setReleasedKeyGraphics(key);
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
             mIsShowingMoreKeysPanel = false;
@@ -614,19 +657,19 @@
         if (mKeyAlreadyProcessed)
             return;
         if (!mIsRepeatableKey) {
-            detectAndSendKey(keyIndex, keyX, keyY);
+            detectAndSendKey(key, keyX, keyY);
         }
     }
 
-    public void onShowMoreKeysPanel(int x, int y, long eventTime, KeyEventHandler handler) {
+    public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
         onLongPressed();
-        onDownEvent(x, y, eventTime, handler);
+        onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
         mIsShowingMoreKeysPanel = true;
     }
 
     public void onLongPressed() {
         mKeyAlreadyProcessed = true;
-        setReleasedKeyGraphics(mKeyIndex);
+        setReleasedKeyGraphics(mCurrentKey);
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
             queue.remove(this);
@@ -647,8 +690,7 @@
 
     private void onCancelEventInternal() {
         mTimerProxy.cancelKeyTimers();
-        mDrawingProxy.cancelShowKeyPreview(this);
-        setReleasedKeyGraphics(mKeyIndex);
+        setReleasedKeyGraphics(mCurrentKey);
         mIsInSlidingKeyInput = false;
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
@@ -656,108 +698,61 @@
         }
     }
 
-    private void startRepeatKey(int keyIndex) {
-        final Key key = getKey(keyIndex);
-        if (key != null && key.mRepeatable) {
-            onRepeatKey(keyIndex);
-            mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, keyIndex, this);
+    private void startRepeatKey(Key key) {
+        if (key != null && key.isRepeatable()) {
+            onRepeatKey(key);
+            mTimerProxy.startKeyRepeatTimer(this);
             mIsRepeatableKey = true;
         } else {
             mIsRepeatableKey = false;
         }
     }
 
-    public void onRepeatKey(int keyIndex) {
-        Key key = getKey(keyIndex);
+    public void onRepeatKey(Key key) {
         if (key != null) {
-            detectAndSendKey(keyIndex, key.mX, key.mY);
+            detectAndSendKey(key, key.mX, key.mY);
         }
     }
 
-    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) {
-        if (mKeys == null || mKeyDetector == null)
+    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
+        if (mKeyDetector == null)
             throw new NullPointerException("keyboard and/or key detector not set");
-        int curKey = mKeyIndex;
+        Key curKey = mCurrentKey;
         if (newKey == curKey) {
             return false;
-        } else if (isValidKeyIndex(curKey)) {
-            return mKeys.get(curKey).squaredDistanceToEdge(x, y)
+        } else if (curKey != null) {
+            return curKey.squaredDistanceToEdge(x, y)
                     >= mKeyDetector.getKeyHysteresisDistanceSquared();
         } else {
             return true;
         }
     }
 
-    private void startLongPressTimer(int keyIndex) {
-        Key key = getKey(keyIndex);
-        if (key == null) return;
-        if (key.mCode == Keyboard.CODE_SHIFT) {
-            if (sLongPressShiftKeyTimeout > 0) {
-                mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, keyIndex, this);
-            }
-        } else if (key.mCode == Keyboard.CODE_SPACE) {
-            if (sLongPressSpaceKeyTimeout > 0) {
-                mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, keyIndex, this);
-            }
-        } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) {
-            // We need not start long press timer on the key which has manual temporary upper case
-            // code defined and the keyboard is in manual temporary upper case mode.
-            return;
-        } else if (sKeyboardSwitcher.isInMomentarySwitchState()) {
-            // We use longer timeout for sliding finger input started from the symbols mode key.
-            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, keyIndex, this);
-        } else {
-            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this);
+    private void startLongPressTimer(Key key) {
+        if (key != null && key.isLongPressEnabled()) {
+            mTimerProxy.startLongPressTimer(this);
         }
     }
 
-    private void detectAndSendKey(int index, int x, int y) {
-        final Key key = getKey(index);
+    private void detectAndSendKey(Key key, int x, int y) {
         if (key == null) {
             callListenerOnCancelInput();
             return;
         }
-        if (key.mOutputText != null) {
-            callListenerOnTextInput(key);
-            callListenerOnRelease(key, key.mCode, false);
-        } else {
-            int code = key.mCode;
-            final int[] codes = mKeyDetector.newCodeArray();
-            mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
 
-            // If keyboard is in manual temporary upper case state and key has manual temporary
-            // uppercase letter as key hint letter, alternate character code should be sent.
-            if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) {
-                code = key.mHintLabel.charAt(0);
-                codes[0] = code;
-            }
-
-            // 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;
-            }
-            callListenerOnCodeInput(key, code, codes, x, y);
-            callListenerOnRelease(key, code, false);
-        }
+        int code = key.mCode;
+        callListenerOnCodeInput(key, code, x, y);
+        callListenerOnRelease(key, code, false);
     }
 
     private long mPreviousEventTime;
 
     private void printTouchEvent(String title, int x, int y, long eventTime) {
-        final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
-        final Key key = getKey(keyIndex);
-        final String code = (key == null) ? "----" : keyCodePrintable(key.mCode);
+        final Key key = mKeyDetector.detectHitKey(x, y);
+        final String code = KeyDetector.printableCode(key);
         final long delta = eventTime - mPreviousEventTime;
-        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
-                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
+        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
+                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code));
         mPreviousEventTime = eventTime;
     }
-
-    private static String keyCodePrintable(int primaryCode) {
-        final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
-        return  String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 2a25d0c..6b59a8d 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -17,20 +17,20 @@
 package com.android.inputmethod.keyboard;
 
 import android.graphics.Rect;
+import android.text.TextUtils;
 
-import com.android.inputmethod.keyboard.internal.KeyboardParams.TouchPositionCorrection;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
+import com.android.inputmethod.latin.JniUtils;
 import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
 
 public class ProximityInfo {
     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
     /** Number of key widths from current touch point to search for nearest keys. */
     private static float SEARCH_DISTANCE = 1.2f;
-    private static final int[] EMPTY_INT_ARRAY = new int[0];
+    private static final Key[] EMPTY_KEY_ARRAY = new Key[0];
 
     private final int mKeyHeight;
     private final int mGridWidth;
@@ -41,10 +41,18 @@
     // TODO: Find a proper name for mKeyboardMinWidth
     private final int mKeyboardMinWidth;
     private final int mKeyboardHeight;
-    private final int[][] mGridNeighbors;
+    private final int mMostCommonKeyWidth;
+    private final Key[][] mGridNeighbors;
+    private final String mLocaleStr;
 
-    ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth,
-            int keyHeight, List<Key> keys, TouchPositionCorrection touchPositionCorrection) {
+    ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height,
+            int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys,
+            TouchPositionCorrection touchPositionCorrection) {
+        if (TextUtils.isEmpty(localeStr)) {
+            mLocaleStr = "";
+        } else {
+            mLocaleStr = localeStr;
+        }
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -52,60 +60,72 @@
         mCellHeight = (height + mGridHeight - 1) / mGridHeight;
         mKeyboardMinWidth = minWidth;
         mKeyboardHeight = height;
-        mKeyHeight = keyHeight;
-        mGridNeighbors = new int[mGridSize][];
+        mKeyHeight = mostCommonKeyHeight;
+        mMostCommonKeyWidth = mostCommonKeyWidth;
+        mGridNeighbors = new Key[mGridSize][];
         if (minWidth == 0 || height == 0) {
-            // No proximity required. Keyboard might be mini keyboard.
+            // No proximity required. Keyboard might be more keys keyboard.
             return;
         }
-        computeNearestNeighbors(keyWidth, keys, touchPositionCorrection);
+        computeNearestNeighbors(
+                mostCommonKeyWidth, keys, touchPositionCorrection);
     }
 
     public static ProximityInfo createDummyProximityInfo() {
-        return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptyList(), null);
+        return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
     }
 
-    public static ProximityInfo createSpellCheckerProximityInfo() {
+    public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) {
         final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
         spellCheckerProximityInfo.mNativeProximityInfo =
-                spellCheckerProximityInfo.setProximityInfoNative(
+                spellCheckerProximityInfo.setProximityInfoNative("",
                         SpellCheckerProximityInfo.ROW_SIZE,
-                        480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY,
-                        0, null, null, null, null, null, null, null, null);
+                        SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
+                        SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT,
+                        SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
+                        SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT,
+                        1, proximity, 0, null, null, null, null, null, null, null, null);
         return spellCheckerProximityInfo;
     }
 
-    private int mNativeProximityInfo;
+    private long mNativeProximityInfo;
     static {
-        Utils.loadNativeLibrary();
+        JniUtils.loadNativeLibrary();
     }
-    private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
-            int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray,
+
+    private native long setProximityInfoNative(
+            String locale, int maxProximityCharsSize, int displayWidth,
+            int displayHeight, int gridWidth, int gridHeight,
+            int mostCommonKeyWidth, int[] proximityCharsArray,
             int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
             int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
             float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
-    private native void releaseProximityInfoNative(int nativeProximityInfo);
 
-    private final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth,
-            int keyboardHeight, List<Key> keys,
-            TouchPositionCorrection touchPositionCorrection) {
-        int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
+    private native void releaseProximityInfoNative(long nativeProximityInfo);
+
+    private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth,
+            int keyboardHeight, final Key[] keys, TouchPositionCorrection touchPositionCorrection) {
+        final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
         Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
         for (int i = 0; i < mGridSize; ++i) {
-            final int proximityCharsLength = gridNeighborKeyIndexes[i].length;
+            final int proximityCharsLength = gridNeighborKeys[i].length;
             for (int j = 0; j < proximityCharsLength; ++j) {
                 proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] =
-                        keys.get(gridNeighborKeyIndexes[i][j]).mCode;
+                        gridNeighborKeys[i][j].mCode;
             }
         }
-        final int keyCount = keys.size();
+        final int keyCount = keys.length;
         final int[] keyXCoordinates = new int[keyCount];
         final int[] keyYCoordinates = new int[keyCount];
         final int[] keyWidths = new int[keyCount];
         final int[] keyHeights = new int[keyCount];
         final int[] keyCharCodes = new int[keyCount];
+        final float[] sweetSpotCenterXs;
+        final float[] sweetSpotCenterYs;
+        final float[] sweetSpotRadii;
+
         for (int i = 0; i < keyCount; ++i) {
-            final Key key = keys.get(i);
+            final Key key = keys[i];
             keyXCoordinates[i] = key.mX;
             keyYCoordinates[i] = key.mY;
             keyWidths[i] = key.mWidth;
@@ -113,51 +133,40 @@
             keyCharCodes[i] = key.mCode;
         }
 
-        float[] sweetSpotCenterXs = null;
-        float[] sweetSpotCenterYs = null;
-        float[] sweetSpotRadii = null;
-
         if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
             sweetSpotCenterXs = new float[keyCount];
             sweetSpotCenterYs = new float[keyCount];
             sweetSpotRadii = new float[keyCount];
-            calculateSweetSpot(keys, touchPositionCorrection,
-                    sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+            for (int i = 0; i < keyCount; i++) {
+                final Key key = keys[i];
+                final Rect hitBox = key.mHitBox;
+                final int row = hitBox.top / mKeyHeight;
+                if (row < touchPositionCorrection.mRadii.length) {
+                    final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f;
+                    final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f;
+                    final float hitBoxWidth = hitBox.right - hitBox.left;
+                    final float hitBoxHeight = hitBox.bottom - hitBox.top;
+                    final float x = touchPositionCorrection.mXs[row];
+                    final float y = touchPositionCorrection.mYs[row];
+                    final float radius = touchPositionCorrection.mRadii[row];
+                    sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
+                    sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
+                    sweetSpotRadii[i] = radius * (float) Math.sqrt(
+                            hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
+                }
+            }
+        } else {
+            sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
         }
 
-        mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
-                keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray,
+        mNativeProximityInfo = setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
+                keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth,
+                proximityCharsArray,
                 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
                 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
     }
 
-    private void calculateSweetSpot(List<Key> keys, TouchPositionCorrection touchPositionCorrection,
-            float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii) {
-        final int keyCount = keys.size();
-        final float[] xs = touchPositionCorrection.mXs;
-        final float[] ys = touchPositionCorrection.mYs;
-        final float[] radii = touchPositionCorrection.mRadii;
-        for (int i = 0; i < keyCount; ++i) {
-            final Key key = keys.get(i);
-            final Rect hitBox = key.mHitBox;
-            final int row = hitBox.top / mKeyHeight;
-            if (row < radii.length) {
-                final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f;
-                final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f;
-                final float hitBoxWidth = hitBox.right - hitBox.left;
-                final float hitBoxHeight = hitBox.bottom - hitBox.top;
-                final float x = xs[row];
-                final float y = ys[row];
-                final float radius = radii[row];
-                sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
-                sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
-                sweetSpotRadii[i] = radius
-                        * (float)Math.sqrt(hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
-            }
-        }
-    }
-
-    public int getNativeProximityInfo() {
+    public long getNativeProximityInfo() {
         return mNativeProximityInfo;
     }
 
@@ -173,12 +182,16 @@
         }
     }
 
-    private void computeNearestNeighbors(int defaultWidth, List<Key> keys,
+    private void computeNearestNeighbors(int defaultWidth, final Key[] keys,
             TouchPositionCorrection touchPositionCorrection) {
+        final HashMap<Integer, Key> keyCodeMap = new HashMap<Integer, Key>();
+        for (final Key key : keys) {
+            keyCodeMap.put(key.mCode, key);
+        }
         final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
         final int threshold = thresholdBase * thresholdBase;
         // Round-up so we don't have any pixels outside the grid
-        final int[] indices = new int[keys.size()];
+        final Key[] neighborKeys = new Key[keys.length];
         final int gridWidth = mGridWidth * mCellWidth;
         final int gridHeight = mGridHeight * mCellHeight;
         for (int x = 0; x < gridWidth; x += mCellWidth) {
@@ -186,31 +199,55 @@
                 final int centerX = x + mCellWidth / 2;
                 final int centerY = y + mCellHeight / 2;
                 int count = 0;
-                for (int i = 0; i < keys.size(); i++) {
-                    final Key key = keys.get(i);
+                for (final Key key : keys) {
                     if (key.isSpacer()) continue;
-                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold)
-                        indices[count++] = i;
+                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
+                        neighborKeys[count++] = key;
+                    }
                 }
-                final int[] cell = new int[count];
-                System.arraycopy(indices, 0, cell, 0, count);
-                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = cell;
+                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
+                        Arrays.copyOfRange(neighborKeys, 0, count);
             }
         }
         setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys,
                 touchPositionCorrection);
     }
 
-    public int[] getNearestKeys(int x, int y) {
+    public void fillArrayWithNearestKeyCodes(int x, int y, int primaryKeyCode, int[] dest) {
+        final int destLength = dest.length;
+        if (destLength < 1) {
+            return;
+        }
+        int index = 0;
+        if (primaryKeyCode > Keyboard.CODE_SPACE) {
+            dest[index++] = primaryKeyCode;
+        }
+        final Key[] nearestKeys = getNearestKeys(x, y);
+        for (Key key : nearestKeys) {
+            if (index >= destLength) {
+                break;
+            }
+            final int code = key.mCode;
+            if (code <= Keyboard.CODE_SPACE) {
+                break;
+            }
+            dest[index++] = code;
+        }
+        if (index < destLength) {
+            dest[index] = KeyDetector.NOT_A_CODE;
+        }
+    }
+
+    public Key[] getNearestKeys(int x, int y) {
         if (mGridNeighbors == null) {
-            return EMPTY_INT_ARRAY;
+            return EMPTY_KEY_ARRAY;
         }
         if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
-            int index = (y /  mCellHeight) * mGridWidth + (x / mCellWidth);
+            int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
             if (index < mGridSize) {
                 return mGridNeighbors[index];
             }
         }
-        return EMPTY_INT_ARRAY;
+        return EMPTY_KEY_ARRAY;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
index 62a9259..347383f 100644
--- a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
@@ -17,12 +17,12 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.os.Build;
 import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Utils;
 
 public class SuddenJumpingTouchEventHandler {
     private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName();
@@ -49,18 +49,8 @@
 
     public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
         mView = view;
-        final String[] deviceList = context.getResources().getStringArray(
-                R.array.sudden_jumping_touch_event_device_list);
-        mNeedsSuddenJumpingHack = needsSuddenJumpingHack(Build.HARDWARE, deviceList);
-    }
-
-    private static boolean needsSuddenJumpingHack(String deviceName, String[] deviceList) {
-        for (String device : deviceList) {
-            if (device.equalsIgnoreCase(deviceName)) {
-                return true;
-            }
-        }
-        return false;
+        mNeedsSuddenJumpingHack = Boolean.parseBoolean(Utils.getDeviceOverrideValue(
+                context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false"));
     }
 
     public void setKeyboard(Keyboard newKeyboard) {
diff --git a/java/src/com/android/inputmethod/compat/FrameLayoutCompatUtils.java b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
similarity index 73%
rename from java/src/com/android/inputmethod/compat/FrameLayoutCompatUtils.java
rename to java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
index 523bf7d..ee50470 100644
--- a/java/src/com/android/inputmethod/compat/FrameLayoutCompatUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.compat;
+package com.android.inputmethod.keyboard;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -22,20 +22,9 @@
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
-public class FrameLayoutCompatUtils {
-    private static final boolean NEEDS_FRAME_LAYOUT_HACK = (
-            android.os.Build.VERSION.SDK_INT < 11 /* Honeycomb */);
-
-    public static ViewGroup getPlacer(ViewGroup container) {
-        if (NEEDS_FRAME_LAYOUT_HACK) {
-            // Insert RelativeLayout to be able to setMargin because pre-Honeycomb FrameLayout
-            // could not handle setMargin properly.
-            final ViewGroup placer = new RelativeLayout(container.getContext());
-            container.addView(placer);
-            return placer;
-        } else {
-            return container;
-        }
+public class ViewLayoutUtils {
+    private ViewLayoutUtils() {
+        // This utility class is not publicly instantiable.
     }
 
     public static MarginLayoutParams newLayoutParam(ViewGroup placer, int width, int height) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
similarity index 70%
rename from java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
rename to java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
index 28a53ce..5712df1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
@@ -18,29 +18,27 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
+public class AlphabetShiftState {
+    private static final String TAG = AlphabetShiftState.class.getSimpleName();
+    private static final boolean DEBUG = false;
 
-public class KeyboardShiftState {
-    private static final String TAG = KeyboardShiftState.class.getSimpleName();
-    private static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
-
-    private static final int NORMAL = 0;
+    private static final int UNSHIFTED = 0;
     private static final int MANUAL_SHIFTED = 1;
     private static final int MANUAL_SHIFTED_FROM_AUTO = 2;
-    private static final int AUTO_SHIFTED = 3;
+    private static final int AUTOMATIC_SHIFTED = 3;
     private static final int SHIFT_LOCKED = 4;
     private static final int SHIFT_LOCK_SHIFTED = 5;
 
-    private int mState = NORMAL;
+    private int mState = UNSHIFTED;
 
-    public boolean setShifted(boolean newShiftState) {
+    public void setShifted(boolean newShiftState) {
         final int oldState = mState;
         if (newShiftState) {
             switch (oldState) {
-            case NORMAL:
+            case UNSHIFTED:
                 mState = MANUAL_SHIFTED;
                 break;
-            case AUTO_SHIFTED:
+            case AUTOMATIC_SHIFTED:
                 mState = MANUAL_SHIFTED_FROM_AUTO;
                 break;
             case SHIFT_LOCKED:
@@ -51,8 +49,8 @@
             switch (oldState) {
             case MANUAL_SHIFTED:
             case MANUAL_SHIFTED_FROM_AUTO:
-            case AUTO_SHIFTED:
-                mState = NORMAL;
+            case AUTOMATIC_SHIFTED:
+                mState = UNSHIFTED;
                 break;
             case SHIFT_LOCK_SHIFTED:
                 mState = SHIFT_LOCKED;
@@ -61,42 +59,36 @@
         }
         if (DEBUG)
             Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this);
-        return mState != oldState;
     }
 
     public void setShiftLocked(boolean newShiftLockState) {
         final int oldState = mState;
         if (newShiftLockState) {
             switch (oldState) {
-            case NORMAL:
+            case UNSHIFTED:
             case MANUAL_SHIFTED:
             case MANUAL_SHIFTED_FROM_AUTO:
-            case AUTO_SHIFTED:
+            case AUTOMATIC_SHIFTED:
                 mState = SHIFT_LOCKED;
                 break;
             }
         } else {
-            switch (oldState) {
-            case SHIFT_LOCKED:
-            case SHIFT_LOCK_SHIFTED:
-                mState = NORMAL;
-                break;
-            }
+            mState = UNSHIFTED;
         }
         if (DEBUG)
             Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState)
                     + " > " + this);
     }
 
-    public void setAutomaticTemporaryUpperCase() {
+    public void setAutomaticShifted() {
         final int oldState = mState;
-        mState = AUTO_SHIFTED;
+        mState = AUTOMATIC_SHIFTED;
         if (DEBUG)
-            Log.d(TAG, "setAutomaticTemporaryUpperCase: " + toString(oldState) + " > " + this);
+            Log.d(TAG, "setAutomaticShifted: " + toString(oldState) + " > " + this);
     }
 
     public boolean isShiftedOrShiftLocked() {
-        return mState != NORMAL;
+        return mState != UNSHIFTED;
     }
 
     public boolean isShiftLocked() {
@@ -107,16 +99,16 @@
         return mState == SHIFT_LOCK_SHIFTED;
     }
 
-    public boolean isAutomaticTemporaryUpperCase() {
-        return mState == AUTO_SHIFTED;
+    public boolean isAutomaticShifted() {
+        return mState == AUTOMATIC_SHIFTED;
     }
 
-    public boolean isManualTemporaryUpperCase() {
+    public boolean isManualShifted() {
         return mState == MANUAL_SHIFTED || mState == MANUAL_SHIFTED_FROM_AUTO
                 || mState == SHIFT_LOCK_SHIFTED;
     }
 
-    public boolean isManualTemporaryUpperCaseFromAuto() {
+    public boolean isManualShiftedFromAutomaticShifted() {
         return mState == MANUAL_SHIFTED_FROM_AUTO;
     }
 
@@ -127,13 +119,13 @@
 
     private static String toString(int state) {
         switch (state) {
-        case NORMAL: return "NORMAL";
+        case UNSHIFTED: return "UNSHIFTED";
         case MANUAL_SHIFTED: return "MANUAL_SHIFTED";
         case MANUAL_SHIFTED_FROM_AUTO: return "MANUAL_SHIFTED_FROM_AUTO";
-        case AUTO_SHIFTED: return "AUTO_SHIFTED";
+        case AUTOMATIC_SHIFTED: return "AUTOMATIC_SHIFTED";
         case SHIFT_LOCKED: return "SHIFT_LOCKED";
         case SHIFT_LOCK_SHIFTED: return "SHIFT_LOCK_SHIFTED";
-        default: return "UKNOWN";
+        default: return "UNKNOWN";
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
new file mode 100644
index 0000000..0aba813
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -0,0 +1,469 @@
+/*
+ * 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.keyboard.internal;
+
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * String parser of moreKeys attribute of Key.
+ * The string is comma separated texts each of which represents one "more key".
+ * - String resource can be embedded into specification @string/name. This is done before parsing
+ *   comma.
+ * Each "more key" specification is one of the following:
+ * - A single letter (Letter)
+ * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
+ * - Icon followed by keyOutputText or code (@icon/icon_name|@integer/key_code)
+ * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' character.
+ * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well.
+ * See {@link KeyboardIconsSet} about icon_name.
+ */
+public class KeySpecParser {
+    private static final boolean DEBUG = LatinImeLogger.sDBG;
+
+    private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
+
+    // Constants for parsing.
+    private static int COMMA = ',';
+    private static final char ESCAPE_CHAR = '\\';
+    private static final char PREFIX_AT = '@';
+    private static final char SUFFIX_SLASH = '/';
+    private static final String PREFIX_STRING = PREFIX_AT + "string" + SUFFIX_SLASH;
+    private static final char LABEL_END = '|';
+    private static final String PREFIX_ICON = PREFIX_AT + "icon" + SUFFIX_SLASH;
+    private static final String PREFIX_CODE = PREFIX_AT + "integer" + SUFFIX_SLASH;
+    private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
+
+    private KeySpecParser() {
+        // Intentional empty constructor for utility class.
+    }
+
+    private static boolean hasIcon(String moreKeySpec) {
+        if (moreKeySpec.startsWith(PREFIX_ICON)) {
+            final int end = indexOfLabelEnd(moreKeySpec, 0);
+            if (end > 0) {
+                return true;
+            }
+            throw new KeySpecParserError("outputText or code not specified: " + moreKeySpec);
+        }
+        return false;
+    }
+
+    private static boolean hasCode(String moreKeySpec) {
+        final int end = indexOfLabelEnd(moreKeySpec, 0);
+        if (end > 0 && end + 1 < moreKeySpec.length()
+                && moreKeySpec.substring(end + 1).startsWith(PREFIX_CODE)) {
+            return true;
+        }
+        return false;
+    }
+
+    private static String parseEscape(String text) {
+        if (text.indexOf(ESCAPE_CHAR) < 0) {
+            return text;
+        }
+        final int length = text.length();
+        final StringBuilder sb = new StringBuilder();
+        for (int pos = 0; pos < length; pos++) {
+            final char c = text.charAt(pos);
+            if (c == ESCAPE_CHAR && pos + 1 < length) {
+                // Skip escape char
+                pos++;
+                sb.append(text.charAt(pos));
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static int indexOfLabelEnd(String moreKeySpec, int start) {
+        if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
+            final int end = moreKeySpec.indexOf(LABEL_END, start);
+            if (end == 0) {
+                throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
+            }
+            return end;
+        }
+        final int length = moreKeySpec.length();
+        for (int pos = start; pos < length; pos++) {
+            final char c = moreKeySpec.charAt(pos);
+            if (c == ESCAPE_CHAR && pos + 1 < length) {
+                // Skip escape char
+                pos++;
+            } else if (c == LABEL_END) {
+                return pos;
+            }
+        }
+        return -1;
+    }
+
+    public static String getLabel(String moreKeySpec) {
+        if (hasIcon(moreKeySpec)) {
+            return null;
+        }
+        final int end = indexOfLabelEnd(moreKeySpec, 0);
+        final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end))
+                : parseEscape(moreKeySpec);
+        if (TextUtils.isEmpty(label)) {
+            throw new KeySpecParserError("Empty label: " + moreKeySpec);
+        }
+        return label;
+    }
+
+    private static String getOutputTextInternal(String moreKeySpec) {
+        final int end = indexOfLabelEnd(moreKeySpec, 0);
+        if (end <= 0) {
+            return null;
+        }
+        if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
+            throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+        }
+        return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
+    }
+
+    public static String getOutputText(String moreKeySpec) {
+        if (hasCode(moreKeySpec)) {
+            return null;
+        }
+        final String outputText = getOutputTextInternal(moreKeySpec);
+        if (outputText != null) {
+            if (StringUtils.codePointCount(outputText) == 1) {
+                // If output text is one code point, it should be treated as a code.
+                // See {@link #getCode(Resources, String)}.
+                return null;
+            }
+            if (!TextUtils.isEmpty(outputText)) {
+                return outputText;
+            }
+            throw new KeySpecParserError("Empty outputText: " + moreKeySpec);
+        }
+        final String label = getLabel(moreKeySpec);
+        if (label == null) {
+            throw new KeySpecParserError("Empty label: " + moreKeySpec);
+        }
+        // Code is automatically generated for one letter label. See {@link getCode()}.
+        return (StringUtils.codePointCount(label) == 1) ? null : label;
+    }
+
+    public static int getCode(Resources res, String moreKeySpec) {
+        if (hasCode(moreKeySpec)) {
+            final int end = indexOfLabelEnd(moreKeySpec, 0);
+            if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
+                throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+            }
+            final int resId = getResourceId(res,
+                    moreKeySpec.substring(end + /* LABEL_END */1 + /* PREFIX_AT */1),
+                    R.string.english_ime_name);
+            final int code = res.getInteger(resId);
+            return code;
+        }
+        final String outputText = getOutputTextInternal(moreKeySpec);
+        if (outputText != null) {
+            // If output text is one code point, it should be treated as a code.
+            // See {@link #getOutputText(String)}.
+            if (StringUtils.codePointCount(outputText) == 1) {
+                return outputText.codePointAt(0);
+            }
+            return Keyboard.CODE_OUTPUT_TEXT;
+        }
+        final String label = getLabel(moreKeySpec);
+        // Code is automatically generated for one letter label.
+        if (StringUtils.codePointCount(label) == 1) {
+            return label.codePointAt(0);
+        }
+        return Keyboard.CODE_OUTPUT_TEXT;
+    }
+
+    public static int getIconId(String moreKeySpec) {
+        if (hasIcon(moreKeySpec)) {
+            final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
+            final String name = moreKeySpec.substring(PREFIX_ICON.length(), end);
+            return KeyboardIconsSet.getIconId(name);
+        }
+        return KeyboardIconsSet.ICON_UNDEFINED;
+    }
+
+    private static <T> ArrayList<T> arrayAsList(T[] array, int start, int end) {
+        if (array == null) {
+            throw new NullPointerException();
+        }
+        if (start < 0 || start > end || end > array.length) {
+            throw new IllegalArgumentException();
+        }
+
+        final ArrayList<T> list = new ArrayList<T>(end - start);
+        for (int i = start; i < end; i++) {
+            list.add(array[i]);
+        }
+        return list;
+    }
+
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    private static String[] filterOutEmptyString(String[] array) {
+        if (array == null) {
+            return EMPTY_STRING_ARRAY;
+        }
+        ArrayList<String> out = null;
+        for (int i = 0; i < array.length; i++) {
+            final String entry = array[i];
+            if (TextUtils.isEmpty(entry)) {
+                if (out == null) {
+                    out = arrayAsList(array, 0, i);
+                }
+            } else if (out != null) {
+                out.add(entry);
+            }
+        }
+        if (out == null) {
+            return array;
+        } else {
+            return out.toArray(new String[out.size()]);
+        }
+    }
+
+    public static String[] insertAddtionalMoreKeys(String[] moreKeySpecs,
+            String[] additionalMoreKeySpecs) {
+        final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
+        final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
+        final int moreKeysCount = moreKeys.length;
+        final int additionalCount = additionalMoreKeys.length;
+        ArrayList<String> out = null;
+        int additionalIndex = 0;
+        for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
+            final String moreKeySpec = moreKeys[moreKeyIndex];
+            if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
+                if (additionalIndex < additionalCount) {
+                    // Replace '%' marker with additional more key specification.
+                    final String additionalMoreKey = additionalMoreKeys[additionalIndex];
+                    if (out != null) {
+                        out.add(additionalMoreKey);
+                    } else {
+                        moreKeys[moreKeyIndex] = additionalMoreKey;
+                    }
+                    additionalIndex++;
+                } else {
+                    // Filter out excessive '%' marker.
+                    if (out == null) {
+                        out = arrayAsList(moreKeys, 0, moreKeyIndex);
+                    }
+                }
+            } else {
+                if (out != null) {
+                    out.add(moreKeySpec);
+                }
+            }
+        }
+        if (additionalCount > 0 && additionalIndex == 0) {
+            // No '%' marker is found in more keys.
+            // Insert all additional more keys to the head of more keys.
+            if (DEBUG && out != null) {
+                throw new RuntimeException("Internal logic error:"
+                        + " moreKeys=" + Arrays.toString(moreKeys)
+                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
+            }
+            out = arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
+            for (int i = 0; i < moreKeysCount; i++) {
+                out.add(moreKeys[i]);
+            }
+        } else if (additionalIndex < additionalCount) {
+            // The number of '%' markers are less than additional more keys.
+            // Append remained additional more keys to the tail of more keys.
+            if (DEBUG && out != null) {
+                throw new RuntimeException("Internal logic error:"
+                        + " moreKeys=" + Arrays.toString(moreKeys)
+                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
+            }
+            out = arrayAsList(moreKeys, 0, moreKeysCount);
+            for (int i = additionalIndex; i < additionalCount; i++) {
+                out.add(additionalMoreKeys[additionalIndex]);
+            }
+        }
+        if (out == null && moreKeysCount > 0) {
+            return moreKeys;
+        } else if (out != null && out.size() > 0) {
+            return out.toArray(new String[out.size()]);
+        } else {
+            return null;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class KeySpecParserError extends RuntimeException {
+        public KeySpecParserError(String message) {
+            super(message);
+        }
+    }
+
+    private static int getResourceId(Resources res, String name, int packageNameResId) {
+        String packageName = res.getResourcePackageName(packageNameResId);
+        int resId = res.getIdentifier(name, null, packageName);
+        if (resId == 0) {
+            throw new RuntimeException("Unknown resource: " + name);
+        }
+        return resId;
+    }
+
+    private static String resolveStringResource(String rawText, Resources res,
+            int packageNameResId) {
+        int level = 0;
+        String text = rawText;
+        StringBuilder sb;
+        do {
+            level++;
+            if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
+                throw new RuntimeException("too many @string/resource indirection: " + text);
+            }
+
+            final int size = text.length();
+            if (size < PREFIX_STRING.length()) {
+                return text;
+            }
+
+            sb = null;
+            for (int pos = 0; pos < size; pos++) {
+                final char c = text.charAt(pos);
+                if (c == PREFIX_AT && text.startsWith(PREFIX_STRING, pos)) {
+                    if (sb == null) {
+                        sb = new StringBuilder(text.substring(0, pos));
+                    }
+                    final int end = searchResourceNameEnd(text, pos + PREFIX_STRING.length());
+                    final String resName = text.substring(pos + 1, end);
+                    final int resId = getResourceId(res, resName, packageNameResId);
+                    sb.append(res.getString(resId));
+                    pos = end - 1;
+                } else if (c == ESCAPE_CHAR) {
+                    if (sb != null) {
+                        // Append both escape character and escaped character.
+                        sb.append(text.substring(pos, Math.min(pos + 2, size)));
+                    }
+                    pos++;
+                } else if (sb != null) {
+                    sb.append(c);
+                }
+            }
+
+            if (sb != null) {
+                text = sb.toString();
+            }
+        } while (sb != null);
+
+        return text;
+    }
+
+    private static int searchResourceNameEnd(String text, int start) {
+        final int size = text.length();
+        for (int pos = start; pos < size; pos++) {
+            final char c = text.charAt(pos);
+            // String resource name should be consisted of [a-z_0-9].
+            if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
+                continue;
+            }
+            return pos;
+        }
+        return size;
+    }
+
+    public static String[] parseCsvString(String rawText, Resources res, int packageNameResId) {
+        final String text = resolveStringResource(rawText, res, packageNameResId);
+        final int size = text.length();
+        if (size == 0) {
+            return null;
+        }
+        if (StringUtils.codePointCount(text) == 1) {
+            return text.codePointAt(0) == COMMA ? null : new String[] { text };
+        }
+
+        ArrayList<String> list = null;
+        int start = 0;
+        for (int pos = 0; pos < size; pos++) {
+            final char c = text.charAt(pos);
+            if (c == COMMA) {
+                // Skip empty entry.
+                if (pos - start > 0) {
+                    if (list == null) {
+                        list = new ArrayList<String>();
+                    }
+                    list.add(text.substring(start, pos));
+                }
+                // Skip comma
+                start = pos + 1;
+            } else if (c == ESCAPE_CHAR) {
+                // Skip escape character and escaped character.
+                pos++;
+            }
+        }
+        final String remain = (size - start > 0) ? text.substring(start) : null;
+        if (list == null) {
+            return remain != null ? new String[] { remain } : null;
+        } else {
+            if (remain != null) {
+                list.add(remain);
+            }
+            return list.toArray(new String[list.size()]);
+        }
+    }
+
+    public static int getIntValue(String[] moreKeys, String key, int defaultValue) {
+        if (moreKeys == null) {
+            return defaultValue;
+        }
+        boolean foundValue = false;
+        int value = defaultValue;
+        for (int i = 0; i < moreKeys.length; i++) {
+            final String moreKeySpec = moreKeys[i];
+            if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
+                continue;
+            }
+            moreKeys[i] = null;
+            try {
+                if (!foundValue) {
+                    value = Integer.parseInt(moreKeySpec.substring(key.length()));
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                        "integer should follow after " + key + ": " + moreKeySpec);
+            }
+        }
+        return value;
+    }
+
+    public static boolean getBooleanValue(String[] moreKeys, String key) {
+        if (moreKeys == null) {
+            return false;
+        }
+        boolean value = false;
+        for (int i = 0; i < moreKeys.length; i++) {
+            final String moreKeySpec = moreKeys[i];
+            if (moreKeySpec == null || !moreKeySpec.equals(key)) {
+                continue;
+            }
+            moreKeys[i] = null;
+            value = true;
+        }
+        return value;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index b385b7a..9e5c227 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -19,16 +19,17 @@
 import android.content.res.TypedArray;
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder.ParseException;
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 
 public class KeyStyles {
-    private static final String TAG = "KeyStyles";
+    private static final String TAG = KeyStyles.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     private final HashMap<String, DeclaredKeyStyle> mStyles =
@@ -36,26 +37,21 @@
     private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle();
 
     public interface KeyStyle {
-        public CharSequence[] getTextArray(TypedArray a, int index);
-        public CharSequence getText(TypedArray a, int index);
+        public String[] getStringArray(TypedArray a, int index);
+        public String getString(TypedArray a, int index);
         public int getInt(TypedArray a, int index, int defaultValue);
-        public int getFlag(TypedArray a, int index, int defaultValue);
-        public boolean getBoolean(TypedArray a, int index, boolean defaultValue);
+        public int getFlag(TypedArray a, int index);
     }
 
-    /* package */ static class EmptyKeyStyle implements KeyStyle {
-        private EmptyKeyStyle() {
-            // Nothing to do.
+    static class EmptyKeyStyle implements KeyStyle {
+        @Override
+        public String[] getStringArray(TypedArray a, int index) {
+            return KeyStyles.parseStringArray(a, index);
         }
 
         @Override
-        public CharSequence[] getTextArray(TypedArray a, int index) {
-            return parseTextArray(a, index);
-        }
-
-        @Override
-        public CharSequence getText(TypedArray a, int index) {
-            return a.getText(index);
+        public String getString(TypedArray a, int index) {
+            return a.getString(index);
         }
 
         @Override
@@ -64,170 +60,127 @@
         }
 
         @Override
-        public int getFlag(TypedArray a, int index, int defaultValue) {
-            return a.getInt(index, defaultValue);
-        }
-
-        @Override
-        public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
-            return a.getBoolean(index, defaultValue);
-        }
-
-        protected static CharSequence[] parseTextArray(TypedArray a, int index) {
-            if (!a.hasValue(index))
-                return null;
-            final CharSequence text = a.getText(index);
-            return parseCsvText(text);
-        }
-
-        /* package */ static CharSequence[] parseCsvText(CharSequence text) {
-            final int size = text.length();
-            if (size == 0) return null;
-            if (size == 1) return new CharSequence[] { text };
-            final StringBuilder sb = new StringBuilder();
-            ArrayList<CharSequence> list = null;
-            int start = 0;
-            for (int pos = 0; pos < size; pos++) {
-                final char c = text.charAt(pos);
-                if (c == ',') {
-                    if (list == null) list = new ArrayList<CharSequence>();
-                    if (sb.length() == 0) {
-                        list.add(text.subSequence(start, pos));
-                    } else {
-                        list.add(sb.toString());
-                        sb.setLength(0);
-                    }
-                    start = pos + 1;
-                    continue;
-                } else if (c == '\\') {
-                    if (start == pos) {
-                        // Skip escape character at the beginning of the value.
-                        start++;
-                        pos++;
-                    } else {
-                        if (start < pos && sb.length() == 0)
-                            sb.append(text.subSequence(start, pos));
-                        pos++;
-                        if (pos < size)
-                            sb.append(text.charAt(pos));
-                    }
-                } else if (sb.length() > 0) {
-                    sb.append(c);
-                }
-            }
-            if (list == null) {
-                return new CharSequence[] { sb.length() > 0 ? sb : text.subSequence(start, size) };
-            } else {
-                list.add(sb.length() > 0 ? sb : text.subSequence(start, size));
-                return list.toArray(new CharSequence[list.size()]);
-            }
+        public int getFlag(TypedArray a, int index) {
+            return a.getInt(index, 0);
         }
     }
 
-    private static class DeclaredKeyStyle extends EmptyKeyStyle {
-        private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>();
+    static class DeclaredKeyStyle implements KeyStyle {
+        private final HashMap<Integer, Object> mStyleAttributes = new HashMap<Integer, Object>();
 
         @Override
-        public CharSequence[] getTextArray(TypedArray a, int index) {
-            return a.hasValue(index)
-                    ? super.getTextArray(a, index) : (CharSequence[])mAttributes.get(index);
+        public String[] getStringArray(TypedArray a, int index) {
+            if (a.hasValue(index)) {
+                return parseStringArray(a, index);
+            }
+            return (String[])mStyleAttributes.get(index);
         }
 
         @Override
-        public CharSequence getText(TypedArray a, int index) {
-            return a.hasValue(index)
-                    ? super.getText(a, index) : (CharSequence)mAttributes.get(index);
+        public String getString(TypedArray a, int index) {
+            if (a.hasValue(index)) {
+                return a.getString(index);
+            }
+            return (String)mStyleAttributes.get(index);
         }
 
         @Override
         public int getInt(TypedArray a, int index, int defaultValue) {
-            final Integer value = (Integer)mAttributes.get(index);
-            return super.getInt(a, index, (value != null) ? value : defaultValue);
+            if (a.hasValue(index)) {
+                return a.getInt(index, defaultValue);
+            }
+            final Integer styleValue = (Integer)mStyleAttributes.get(index);
+            return styleValue != null ? styleValue : defaultValue;
         }
 
         @Override
-        public int getFlag(TypedArray a, int index, int defaultValue) {
-            final Integer value = (Integer)mAttributes.get(index);
-            return super.getFlag(a, index, defaultValue) | (value != null ? value : 0);
+        public int getFlag(TypedArray a, int index) {
+            final int value = a.getInt(index, 0);
+            final Integer styleValue = (Integer)mStyleAttributes.get(index);
+            return (styleValue != null ? styleValue : 0) | value;
         }
 
-        @Override
-        public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
-            final Boolean value = (Boolean)mAttributes.get(index);
-            return super.getBoolean(a, index, (value != null) ? value : defaultValue);
-        }
-
-        private DeclaredKeyStyle() {
-            super();
-        }
-
-        private void parseKeyStyleAttributes(TypedArray keyAttr) {
+        void readKeyAttributes(TypedArray keyAttr) {
             // TODO: Currently not all Key attributes can be declared as style.
             readInt(keyAttr, R.styleable.Keyboard_Key_code);
-            readText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
-            readText(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
-            readText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
-            readTextArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
-            readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption);
+            readInt(keyAttr, R.styleable.Keyboard_Key_altCode);
+            readString(keyAttr, R.styleable.Keyboard_Key_keyLabel);
+            readString(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
+            readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
+            readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
+            readStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
+            readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
             readInt(keyAttr, R.styleable.Keyboard_Key_keyIcon);
+            readInt(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled);
             readInt(keyAttr, R.styleable.Keyboard_Key_keyIconPreview);
-            readInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted);
             readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
             readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType);
-            readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable);
-            readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled);
+            readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
         }
 
-        private void readText(TypedArray a, int index) {
-            if (a.hasValue(index))
-                mAttributes.put(index, a.getText(index));
+        private void readString(TypedArray a, int index) {
+            if (a.hasValue(index)) {
+                mStyleAttributes.put(index, a.getString(index));
+            }
         }
 
         private void readInt(TypedArray a, int index) {
-            if (a.hasValue(index))
-                mAttributes.put(index, a.getInt(index, 0));
+            if (a.hasValue(index)) {
+                mStyleAttributes.put(index, a.getInt(index, 0));
+            }
         }
 
         private void readFlag(TypedArray a, int index) {
-            final Integer value = (Integer)mAttributes.get(index);
-            if (a.hasValue(index))
-                mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
+            final Integer value = (Integer)mStyleAttributes.get(index);
+            if (a.hasValue(index)) {
+                mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
+            }
         }
 
-        private void readBoolean(TypedArray a, int index) {
-            if (a.hasValue(index))
-                mAttributes.put(index, a.getBoolean(index, false));
+        private void readStringArray(TypedArray a, int index) {
+            final String[] value = parseStringArray(a, index);
+            if (value != null) {
+                mStyleAttributes.put(index, value);
+            }
         }
 
-        private void readTextArray(TypedArray a, int index) {
-            final CharSequence[] value = parseTextArray(a, index);
-            if (value != null)
-                mAttributes.put(index, value);
-        }
-
-        private void addParent(DeclaredKeyStyle parentStyle) {
-            mAttributes.putAll(parentStyle.mAttributes);
+        void addParentStyleAttributes(DeclaredKeyStyle parentStyle) {
+            mStyleAttributes.putAll(parentStyle.mStyleAttributes);
         }
     }
 
+    static String[] parseStringArray(TypedArray a, int index) {
+        if (a.hasValue(index)) {
+            return KeySpecParser.parseCsvString(
+                    a.getString(index), a.getResources(), R.string.english_ime_name);
+        }
+        return null;
+    }
+
     public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs,
-            XmlPullParser parser) {
+            XmlPullParser parser) throws XmlPullParserException {
         final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
-        if (DEBUG) Log.d(TAG, String.format("<%s styleName=%s />",
-                KeyboardBuilder.TAG_KEY_STYLE, styleName));
-        if (mStyles.containsKey(styleName))
-            throw new ParseException("duplicate key style declared: " + styleName, parser);
+        if (DEBUG) {
+            Log.d(TAG, String.format("<%s styleName=%s />",
+                    Keyboard.Builder.TAG_KEY_STYLE, styleName));
+            if (mStyles.containsKey(styleName)) {
+                Log.d(TAG, "key-style " + styleName + " is overridden at "
+                        + parser.getPositionDescription());
+            }
+        }
 
         final DeclaredKeyStyle style = new DeclaredKeyStyle();
         if (keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) {
             final String parentStyle = keyStyleAttr.getString(
                     R.styleable.Keyboard_KeyStyle_parentStyle);
             final DeclaredKeyStyle parent = mStyles.get(parentStyle);
-            if (parent == null)
-                throw new ParseException("Unknown parentStyle " + parentStyle, parser);
-            style.addParent(parent);
+            if (parent == null) {
+                throw new XmlParseUtils.ParseException(
+                        "Unknown parentStyle " + parentStyle, parser);
+            }
+            style.addParentStyleAttributes(parent);
         }
-        style.parseKeyStyleAttributes(keyAttrs);
+        style.readKeyAttributes(keyAttrs);
         mStyles.put(styleName, style);
     }
 
@@ -235,7 +188,7 @@
         return mStyles.get(styleName);
     }
 
-    public KeyStyle getEmptyKeyStyle() {
+    public static KeyStyle getEmptyKeyStyle() {
         return EMPTY_KEY_STYLE;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
deleted file mode 100644
index de64639..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ /dev/null
@@ -1,893 +0,0 @@
-/*
- * 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.keyboard.internal;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
-import android.util.Xml;
-import android.view.InflateException;
-
-import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-/**
- * Keyboard Building helper.
- *
- * This class parses Keyboard XML file and eventually build a Keyboard.
- * The Keyboard XML file looks like:
- * <pre>
- *   &gt;!-- xml/keyboard.xml --&lt;
- *   &gt;Keyboard keyboard_attributes*&lt;
- *     &gt;!-- Keyboard Content --&lt;
- *     &gt;Row row_attributes*&lt;
- *       &gt;!-- Row Content --&lt;
- *       &gt;Key key_attributes* /&lt;
- *       &gt;Spacer horizontalGap="0.2in" /&lt;
- *       &gt;include keyboardLayout="@xml/other_keys"&lt;
- *       ...
- *     &gt;/Row&lt;
- *     &gt;include keyboardLayout="@xml/other_rows"&lt;
- *     ...
- *   &gt;/Keyboard&lt;
- * </pre>
- * The XML file which is included in other file must have &gt;merge&lt; as root element, such as:
- * <pre>
- *   &gt;!-- xml/other_keys.xml --&lt;
- *   &gt;merge&lt;
- *     &gt;Key key_attributes* /&lt;
- *     ...
- *   &gt;/merge&lt;
- * </pre>
- * and
- * <pre>
- *   &gt;!-- xml/other_rows.xml --&lt;
- *   &gt;merge&lt;
- *     &gt;Row row_attributes*&lt;
- *       &gt;Key key_attributes* /&lt;
- *     &gt;/Row&lt;
- *     ...
- *   &gt;/merge&lt;
- * </pre>
- * You can also use switch-case-default tags to select Rows and Keys.
- * <pre>
- *   &gt;switch&lt;
- *     &gt;case case_attribute*&lt;
- *       &gt;!-- Any valid tags at switch position --&lt;
- *     &gt;/case&lt;
- *     ...
- *     &gt;default&lt;
- *       &gt;!-- Any valid tags at switch position --&lt;
- *     &gt;/default&lt;
- *   &gt;/switch&lt;
- * </pre>
- * You can declare Key style and specify styles within Key tags.
- * <pre>
- *     &gt;switch&lt;
- *       &gt;case mode="email"&lt;
- *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
- *           keyLabel=".com"
- *         /&lt;
- *       &gt;/case&lt;
- *       &gt;case mode="url"&lt;
- *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
- *           keyLabel="http://"
- *         /&lt;
- *       &gt;/case&lt;
- *     &gt;/switch&lt;
- *     ...
- *     &gt;Key keyStyle="shift-key" ... /&lt;
- * </pre>
- */
-
-public class KeyboardBuilder<KP extends KeyboardParams> {
-    private static final String TAG = KeyboardBuilder.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    // Keyboard XML Tags
-    private static final String TAG_KEYBOARD = "Keyboard";
-    private static final String TAG_ROW = "Row";
-    private static final String TAG_KEY = "Key";
-    private static final String TAG_SPACER = "Spacer";
-    private static final String TAG_INCLUDE = "include";
-    private static final String TAG_MERGE = "merge";
-    private static final String TAG_SWITCH = "switch";
-    private static final String TAG_CASE = "case";
-    private static final String TAG_DEFAULT = "default";
-    public static final String TAG_KEY_STYLE = "key-style";
-
-    private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
-    private static final int DEFAULT_KEYBOARD_ROWS = 4;
-
-    protected final KP mParams;
-    protected final Context mContext;
-    protected final Resources mResources;
-    private final DisplayMetrics mDisplayMetrics;
-
-    private int mCurrentY = 0;
-    private Row mCurrentRow = null;
-    private boolean mLeftEdge;
-    private boolean mTopEdge;
-    private Key mRightEdgeKey = null;
-    private final KeyStyles mKeyStyles = new KeyStyles();
-
-    /**
-     * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
-     * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
-     * defines.
-     */
-    public static class Row {
-        // keyWidth enum constants
-        private static final int KEYWIDTH_NOT_ENUM = 0;
-        private static final int KEYWIDTH_FILL_RIGHT = -1;
-        private static final int KEYWIDTH_FILL_BOTH = -2;
-
-        private final KeyboardParams mParams;
-        /** Default width of a key in this row. */
-        public final float mDefaultKeyWidth;
-        /** Default height of a key in this row. */
-        public final int mRowHeight;
-
-        private final int mCurrentY;
-        // Will be updated by {@link Key}'s constructor.
-        private float mCurrentX;
-
-        public Row(Resources res, KeyboardParams params, XmlPullParser parser, int y) {
-            mParams = params;
-            TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard);
-            mRowHeight = (int)KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_rowHeight, params.mBaseHeight, params.mDefaultRowHeight);
-            keyboardAttr.recycle();
-            TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard_Key);
-            mDefaultKeyWidth = KeyboardBuilder.getDimensionOrFraction(keyAttr,
-                    R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth, params.mDefaultKeyWidth);
-            keyAttr.recycle();
-
-            mCurrentY = y;
-            mCurrentX = 0.0f;
-        }
-
-        public void setXPos(float keyXPos) {
-            mCurrentX = keyXPos;
-        }
-
-        public void advanceXPos(float width) {
-            mCurrentX += width;
-        }
-
-        public int getKeyY() {
-            return mCurrentY;
-        }
-
-        public float getKeyX(TypedArray keyAttr) {
-            final int widthType = KeyboardBuilder.getEnumValue(keyAttr,
-                    R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
-            if (widthType == KEYWIDTH_FILL_BOTH) {
-                // If keyWidth is fillBoth, the key width should start right after the nearest key
-                // on the left hand side.
-                return mCurrentX;
-            }
-
-            final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
-            if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
-                final float keyXPos = KeyboardBuilder.getDimensionOrFraction(keyAttr,
-                        R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
-                if (keyXPos < 0) {
-                    // If keyXPos is negative, the actual x-coordinate will be
-                    // keyboardWidth + keyXPos.
-                    // keyXPos shouldn't be less than mCurrentX because drawable area for this key
-                    // starts at mCurrentX. Or, this key will overlaps the adjacent key on its left
-                    // hand side.
-                    return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
-                } else {
-                    return keyXPos + mParams.mHorizontalEdgesPadding;
-                }
-            }
-            return mCurrentX;
-        }
-
-        public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
-            final int widthType = KeyboardBuilder.getEnumValue(keyAttr,
-                    R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
-            switch (widthType) {
-            case KEYWIDTH_FILL_RIGHT:
-            case KEYWIDTH_FILL_BOTH:
-                final int keyboardRightEdge =
-                        mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
-                // If keyWidth is fillRight, the actual key width will be determined to fill out the
-                // area up to the right edge of the keyboard.
-                // If keyWidth is fillBoth, the actual key width will be determined to fill out the
-                // area between the nearest key on the left hand side and the right edge of the
-                // keyboard.
-                return keyboardRightEdge - keyXPos;
-            default: // KEYWIDTH_NOT_ENUM
-                return KeyboardBuilder.getDimensionOrFraction(keyAttr,
-                        R.styleable.Keyboard_Key_keyWidth, mParams.mBaseWidth, mDefaultKeyWidth);
-            }
-        }
-    }
-
-    public KeyboardBuilder(Context context, KP params) {
-        mContext = context;
-        final Resources res = context.getResources();
-        mResources = res;
-        mDisplayMetrics = res.getDisplayMetrics();
-
-        mParams = params;
-
-        setTouchPositionCorrectionData(context, params);
-
-        params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
-        params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
-    }
-
-    private static void setTouchPositionCorrectionData(Context context, KeyboardParams params) {
-        final TypedArray a = context.obtainStyledAttributes(
-                null, R.styleable.Keyboard, R.attr.keyboardStyle, 0);
-        params.mThemeId = a.getInt(R.styleable.Keyboard_themeId, 0);
-        final int resourceId = a.getResourceId(R.styleable.Keyboard_touchPositionCorrectionData, 0);
-        a.recycle();
-        if (resourceId == 0) {
-            if (LatinImeLogger.sDBG)
-                throw new RuntimeException("touchPositionCorrectionData is not defined");
-            return;
-        }
-
-        final String[] data = context.getResources().getStringArray(resourceId);
-        params.mTouchPositionCorrection.load(data);
-    }
-
-    public KeyboardBuilder<KP> load(KeyboardId id) {
-        mParams.mId = id;
-        final XmlResourceParser parser = mResources.getXml(id.getXmlId());
-        try {
-            parseKeyboard(parser);
-        } catch (XmlPullParserException e) {
-            Log.w(TAG, "keyboard XML parse error: " + e);
-            throw new IllegalArgumentException(e);
-        } catch (IOException e) {
-            Log.w(TAG, "keyboard XML parse error: " + e);
-            throw new RuntimeException(e);
-        } finally {
-            parser.close();
-        }
-        return this;
-    }
-
-    public void setTouchPositionCorrectionEnabled(boolean enabled) {
-        mParams.mTouchPositionCorrection.setEnabled(enabled);
-    }
-
-    public Keyboard build() {
-        return new Keyboard(mParams);
-    }
-
-    private void parseKeyboard(XmlResourceParser parser)
-            throws XmlPullParserException, IOException {
-        if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mParams.mId));
-        int event;
-        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (event == XmlPullParser.START_TAG) {
-                final String tag = parser.getName();
-                if (TAG_KEYBOARD.equals(tag)) {
-                    parseKeyboardAttributes(parser);
-                    startKeyboard();
-                    parseKeyboardContent(parser, false);
-                    break;
-                } else {
-                    throw new IllegalStartTag(parser, TAG_KEYBOARD);
-                }
-            }
-        }
-    }
-
-    public static String parseKeyboardLocale(
-            Context context, int resId) throws XmlPullParserException, IOException {
-        final Resources res = context.getResources();
-        final XmlPullParser parser = res.getXml(resId);
-        if (parser == null) return "";
-        int event;
-        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (event == XmlPullParser.START_TAG) {
-                final String tag = parser.getName();
-                if (TAG_KEYBOARD.equals(tag)) {
-                    final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
-                            R.styleable.Keyboard);
-                    final String locale = keyboardAttr.getString(
-                            R.styleable.Keyboard_keyboardLocale);
-                    keyboardAttr.recycle();
-                    return locale;
-                } else {
-                    throw new IllegalStartTag(parser, TAG_KEYBOARD);
-                }
-            }
-        }
-        return "";
-    }
-
-    private void parseKeyboardAttributes(XmlPullParser parser) {
-        final int displayWidth = mDisplayMetrics.widthPixels;
-        final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
-                Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
-                R.style.Keyboard);
-        final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_Key);
-        try {
-            final int displayHeight = mDisplayMetrics.heightPixels;
-            final int keyboardHeight = (int)keyboardAttr.getDimension(
-                    R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
-            final int maxKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
-            int minKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
-            if (minKeyboardHeight < 0) {
-                // Specified fraction was negative, so it should be calculated against display
-                // width.
-                minKeyboardHeight = -(int)getDimensionOrFraction(keyboardAttr,
-                        R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
-            }
-            final KeyboardParams params = mParams;
-            // Keyboard height will not exceed maxKeyboardHeight and will not be less than
-            // minKeyboardHeight.
-            params.mOccupiedHeight = Math.max(
-                    Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
-            params.mOccupiedWidth = params.mId.mWidth;
-            params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
-            params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
-            params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_keyboardHorizontalEdgesPadding, mParams.mOccupiedWidth, 0);
-
-            params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
-                    - params.mHorizontalCenterPadding;
-            params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr,
-                    R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
-                    params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
-            params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
-            params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
-            params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
-                    - params.mBottomPadding + params.mVerticalGap;
-            params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_rowHeight, params.mBaseHeight,
-                    params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
-
-            params.mIsRtlKeyboard = keyboardAttr.getBoolean(
-                    R.styleable.Keyboard_isRtlKeyboard, false);
-            params.mMoreKeysTemplate = keyboardAttr.getResourceId(
-                    R.styleable.Keyboard_moreKeysTemplate, 0);
-            params.mMaxMiniKeyboardColumn = keyAttr.getInt(
-                    R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
-
-            params.mIconsSet.loadIcons(keyboardAttr);
-        } finally {
-            keyAttr.recycle();
-            keyboardAttr.recycle();
-        }
-    }
-
-    private void parseKeyboardContent(XmlPullParser parser, boolean skip)
-            throws XmlPullParserException, IOException {
-        int event;
-        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (event == XmlPullParser.START_TAG) {
-                final String tag = parser.getName();
-                if (TAG_ROW.equals(tag)) {
-                    Row row = parseRowAttributes(parser);
-                    if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW));
-                    if (!skip)
-                        startRow(row);
-                    parseRowContent(parser, row, skip);
-                } else if (TAG_INCLUDE.equals(tag)) {
-                    parseIncludeKeyboardContent(parser, skip);
-                } else if (TAG_SWITCH.equals(tag)) {
-                    parseSwitchKeyboardContent(parser, skip);
-                } else if (TAG_KEY_STYLE.equals(tag)) {
-                    parseKeyStyle(parser, skip);
-                } else {
-                    throw new IllegalStartTag(parser, TAG_ROW);
-                }
-            } else if (event == XmlPullParser.END_TAG) {
-                final String tag = parser.getName();
-                if (TAG_KEYBOARD.equals(tag)) {
-                    endKeyboard();
-                    break;
-                } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
-                        || TAG_MERGE.equals(tag)) {
-                    if (DEBUG) Log.d(TAG, String.format("</%s>", tag));
-                    break;
-                } else if (TAG_KEY_STYLE.equals(tag)) {
-                    continue;
-                } else {
-                    throw new IllegalEndTag(parser, TAG_ROW);
-                }
-            }
-        }
-    }
-
-    private Row parseRowAttributes(XmlPullParser parser) {
-        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard);
-        try {
-            if (a.hasValue(R.styleable.Keyboard_horizontalGap))
-                throw new IllegalAttribute(parser, "horizontalGap");
-            if (a.hasValue(R.styleable.Keyboard_verticalGap))
-                throw new IllegalAttribute(parser, "verticalGap");
-            return new Row(mResources, mParams, parser, mCurrentY);
-        } finally {
-            a.recycle();
-        }
-    }
-
-    private void parseRowContent(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        int event;
-        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (event == XmlPullParser.START_TAG) {
-                final String tag = parser.getName();
-                if (TAG_KEY.equals(tag)) {
-                    parseKey(parser, row, skip);
-                } else if (TAG_SPACER.equals(tag)) {
-                    parseSpacer(parser, row, skip);
-                } else if (TAG_INCLUDE.equals(tag)) {
-                    parseIncludeRowContent(parser, row, skip);
-                } else if (TAG_SWITCH.equals(tag)) {
-                    parseSwitchRowContent(parser, row, skip);
-                } else if (TAG_KEY_STYLE.equals(tag)) {
-                    parseKeyStyle(parser, skip);
-                } else {
-                    throw new IllegalStartTag(parser, TAG_KEY);
-                }
-            } else if (event == XmlPullParser.END_TAG) {
-                final String tag = parser.getName();
-                if (TAG_ROW.equals(tag)) {
-                    if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_ROW));
-                    if (!skip)
-                        endRow(row);
-                    break;
-                } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
-                        || TAG_MERGE.equals(tag)) {
-                    if (DEBUG) Log.d(TAG, String.format("</%s>", tag));
-                    break;
-                } else if (TAG_KEY_STYLE.equals(tag)) {
-                    continue;
-                } else {
-                    throw new IllegalEndTag(parser, TAG_KEY);
-                }
-            }
-        }
-    }
-
-    private void parseKey(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        if (skip) {
-            checkEndTag(TAG_KEY, parser);
-        } else {
-            final Key key = new Key(mResources, mParams, row, parser, mKeyStyles);
-            if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d moreKeys=%s />",
-                    TAG_KEY, (key.isEnabled() ? "" : " disabled"), key.mLabel, key.mCode,
-                    Arrays.toString(key.mMoreKeys)));
-            checkEndTag(TAG_KEY, parser);
-            endKey(key);
-        }
-    }
-
-    private void parseSpacer(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        if (skip) {
-            checkEndTag(TAG_SPACER, parser);
-        } else {
-            final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser, mKeyStyles);
-            if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER));
-            checkEndTag(TAG_SPACER, parser);
-            endKey(spacer);
-        }
-    }
-
-    private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip)
-            throws XmlPullParserException, IOException {
-        parseIncludeInternal(parser, null, skip);
-    }
-
-    private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        parseIncludeInternal(parser, row, skip);
-    }
-
-    private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        if (skip) {
-            checkEndTag(TAG_INCLUDE, parser);
-        } else {
-            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard_Include);
-            final int keyboardLayout = a.getResourceId(
-                    R.styleable.Keyboard_Include_keyboardLayout, 0);
-            a.recycle();
-
-            checkEndTag(TAG_INCLUDE, parser);
-            if (keyboardLayout == 0)
-                throw new ParseException("No keyboardLayout attribute in <include/>", parser);
-            if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />",
-                    TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout)));
-            final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
-            try {
-                parseMerge(parserForInclude, row, skip);
-            } finally {
-                parserForInclude.close();
-            }
-        }
-    }
-
-    private void parseMerge(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        int event;
-        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (event == XmlPullParser.START_TAG) {
-                final String tag = parser.getName();
-                if (TAG_MERGE.equals(tag)) {
-                    if (row == null) {
-                        parseKeyboardContent(parser, skip);
-                    } else {
-                        parseRowContent(parser, row, skip);
-                    }
-                    break;
-                } else {
-                    throw new ParseException(
-                            "Included keyboard layout must have <merge> root element", parser);
-                }
-            }
-        }
-    }
-
-    private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip)
-            throws XmlPullParserException, IOException {
-        parseSwitchInternal(parser, null, skip);
-    }
-
-    private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        parseSwitchInternal(parser, row, skip);
-    }
-
-    private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mParams.mId));
-        boolean selected = false;
-        int event;
-        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (event == XmlPullParser.START_TAG) {
-                final String tag = parser.getName();
-                if (TAG_CASE.equals(tag)) {
-                    selected |= parseCase(parser, row, selected ? true : skip);
-                } else if (TAG_DEFAULT.equals(tag)) {
-                    selected |= parseDefault(parser, row, selected ? true : skip);
-                } else {
-                    throw new IllegalStartTag(parser, TAG_KEY);
-                }
-            } else if (event == XmlPullParser.END_TAG) {
-                final String tag = parser.getName();
-                if (TAG_SWITCH.equals(tag)) {
-                    if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_SWITCH));
-                    break;
-                } else {
-                    throw new IllegalEndTag(parser, TAG_KEY);
-                }
-            }
-        }
-    }
-
-    private boolean parseCase(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        final boolean selected = parseCaseCondition(parser);
-        if (row == null) {
-            // Processing Rows.
-            parseKeyboardContent(parser, selected ? skip : true);
-        } else {
-            // Processing Keys.
-            parseRowContent(parser, row, selected ? skip : true);
-        }
-        return selected;
-    }
-
-    private boolean parseCaseCondition(XmlPullParser parser) {
-        final KeyboardId id = mParams.mId;
-        if (id == null)
-            return true;
-
-        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_Case);
-        try {
-            final boolean modeMatched = matchTypedValue(a,
-                    R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
-            final boolean navigateActionMatched = matchBoolean(a,
-                    R.styleable.Keyboard_Case_navigateAction, id.mNavigateAction);
-            final boolean passwordInputMatched = matchBoolean(a,
-                    R.styleable.Keyboard_Case_passwordInput, id.mPasswordInput);
-            final boolean hasSettingsKeyMatched = matchBoolean(a,
-                    R.styleable.Keyboard_Case_hasSettingsKey, id.mHasSettingsKey);
-            final boolean f2KeyModeMatched = matchInteger(a,
-                    R.styleable.Keyboard_Case_f2KeyMode, id.mF2KeyMode);
-            final boolean clobberSettingsKeyMatched = matchBoolean(a,
-                    R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
-            final boolean shortcutKeyEnabledMatched = matchBoolean(a,
-                    R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
-            final boolean hasShortcutKeyMatched = matchBoolean(a,
-                    R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
-            // As noted at {@link KeyboardId} class, we are interested only in enum value masked by
-            // {@link android.view.inputmethod.EditorInfo#IME_MASK_ACTION} and
-            // {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. So matching
-            // this attribute with id.mImeOptions as integer value is enough for our purpose.
-            final boolean imeActionMatched = matchInteger(a,
-                    R.styleable.Keyboard_Case_imeAction, id.mImeAction);
-            final boolean localeCodeMatched = matchString(a,
-                    R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
-            final boolean languageCodeMatched = matchString(a,
-                    R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
-            final boolean countryCodeMatched = matchString(a,
-                    R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
-            final boolean selected = modeMatched && navigateActionMatched && passwordInputMatched
-                    && hasSettingsKeyMatched && f2KeyModeMatched && clobberSettingsKeyMatched
-                    && shortcutKeyEnabledMatched && hasShortcutKeyMatched && imeActionMatched &&
-                    localeCodeMatched && languageCodeMatched && countryCodeMatched;
-
-            if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
-                    textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
-                    booleanAttr(a, R.styleable.Keyboard_Case_navigateAction, "navigateAction"),
-                    booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"),
-                    booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
-                    textAttr(KeyboardId.f2KeyModeName(
-                            a.getInt(R.styleable.Keyboard_Case_f2KeyMode, -1)), "f2KeyMode"),
-                    booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
-                            "clobberSettingsKey"),
-                    booleanAttr(
-                            a, R.styleable.Keyboard_Case_shortcutKeyEnabled, "shortcutKeyEnabled"),
-                    booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, "hasShortcutKey"),
-                    textAttr(EditorInfoCompatUtils.imeOptionsName(
-                            a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
-                    textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), "localeCode"),
-                    textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"),
-                    textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"),
-                    Boolean.toString(selected)));
-
-            return selected;
-        } finally {
-            a.recycle();
-        }
-    }
-
-    private static boolean matchInteger(TypedArray a, int index, int value) {
-        // If <case> does not have "index" attribute, that means this <case> is wild-card for the
-        // attribute.
-        return !a.hasValue(index) || a.getInt(index, 0) == value;
-    }
-
-    private static boolean matchBoolean(TypedArray a, int index, boolean value) {
-        // If <case> does not have "index" attribute, that means this <case> is wild-card for the
-        // attribute.
-        return !a.hasValue(index) || a.getBoolean(index, false) == value;
-    }
-
-    private static boolean matchString(TypedArray a, int index, String value) {
-        // If <case> does not have "index" attribute, that means this <case> is wild-card for the
-        // attribute.
-        return !a.hasValue(index) || stringArrayContains(a.getString(index).split("\\|"), value);
-    }
-
-    private static boolean matchTypedValue(TypedArray a, int index, int intValue, String strValue) {
-        // If <case> does not have "index" attribute, that means this <case> is wild-card for the
-        // attribute.
-        final TypedValue v = a.peekValue(index);
-        if (v == null)
-            return true;
-
-        if (isIntegerValue(v)) {
-            return intValue == a.getInt(index, 0);
-        } else if (isStringValue(v)) {
-            return stringArrayContains(a.getString(index).split("\\|"), strValue);
-        }
-        return false;
-    }
-
-    private static boolean stringArrayContains(String[] array, String value) {
-        for (final String elem : array) {
-            if (elem.equals(value))
-                return true;
-        }
-        return false;
-    }
-
-    private boolean parseDefault(XmlPullParser parser, Row row, boolean skip)
-            throws XmlPullParserException, IOException {
-        if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
-        if (row == null) {
-            parseKeyboardContent(parser, skip);
-        } else {
-            parseRowContent(parser, row, skip);
-        }
-        return true;
-    }
-
-    private void parseKeyStyle(XmlPullParser parser, boolean skip) {
-        TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_KeyStyle);
-        TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_Key);
-        try {
-            if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
-                throw new ParseException("<" + TAG_KEY_STYLE
-                        + "/> needs styleName attribute", parser);
-            if (!skip)
-                mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
-        } finally {
-            keyStyleAttr.recycle();
-            keyAttrs.recycle();
-        }
-    }
-
-    private static void checkEndTag(String tag, XmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        if (parser.next() == XmlPullParser.END_TAG && tag.equals(parser.getName()))
-            return;
-        throw new NonEmptyTag(tag, parser);
-    }
-
-    private void startKeyboard() {
-        mCurrentY += mParams.mTopPadding;
-        mTopEdge = true;
-    }
-
-    private void startRow(Row row) {
-        addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
-        mCurrentRow = row;
-        mLeftEdge = true;
-        mRightEdgeKey = null;
-    }
-
-    private void endRow(Row row) {
-        if (mCurrentRow == null)
-            throw new InflateException("orphant end row tag");
-        if (mRightEdgeKey != null) {
-            mRightEdgeKey.markAsRightEdge(mParams);
-            mRightEdgeKey = null;
-        }
-        addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
-        mCurrentY += row.mRowHeight;
-        mCurrentRow = null;
-        mTopEdge = false;
-    }
-
-    private void endKey(Key key) {
-        mParams.onAddKey(key);
-        if (mLeftEdge) {
-            key.markAsLeftEdge(mParams);
-            mLeftEdge = false;
-        }
-        if (mTopEdge) {
-            key.markAsTopEdge(mParams);
-        }
-        mRightEdgeKey = key;
-    }
-
-    private void endKeyboard() {
-    }
-
-    private void addEdgeSpace(float width, Row row) {
-        row.advanceXPos(width);
-        mLeftEdge = false;
-        mRightEdgeKey = null;
-    }
-
-    public static float getDimensionOrFraction(TypedArray a, int index, int base, float defValue) {
-        final TypedValue value = a.peekValue(index);
-        if (value == null)
-            return defValue;
-        if (isFractionValue(value)) {
-            return a.getFraction(index, base, base, defValue);
-        } else if (isDimensionValue(value)) {
-            return a.getDimension(index, defValue);
-        }
-        return defValue;
-    }
-
-    public static int getEnumValue(TypedArray a, int index, int defValue) {
-        final TypedValue value = a.peekValue(index);
-        if (value == null)
-            return defValue;
-        if (isIntegerValue(value)) {
-            return a.getInt(index, defValue);
-        }
-        return defValue;
-    }
-
-    private static boolean isFractionValue(TypedValue v) {
-        return v.type == TypedValue.TYPE_FRACTION;
-    }
-
-    private static boolean isDimensionValue(TypedValue v) {
-        return v.type == TypedValue.TYPE_DIMENSION;
-    }
-
-    private static boolean isIntegerValue(TypedValue v) {
-        return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
-    }
-
-    private static boolean isStringValue(TypedValue v) {
-        return v.type == TypedValue.TYPE_STRING;
-    }
-
-    @SuppressWarnings("serial")
-    public static class ParseException extends InflateException {
-        public ParseException(String msg, XmlPullParser parser) {
-            super(msg + " at line " + parser.getLineNumber());
-        }
-    }
-
-    @SuppressWarnings("serial")
-    private static class IllegalStartTag extends ParseException {
-        public IllegalStartTag(XmlPullParser parser, String parent) {
-            super("Illegal start tag " + parser.getName() + " in " + parent, parser);
-        }
-    }
-
-    @SuppressWarnings("serial")
-    private static class IllegalEndTag extends ParseException {
-        public IllegalEndTag(XmlPullParser parser, String parent) {
-            super("Illegal end tag " + parser.getName() + " in " + parent, parser);
-        }
-    }
-
-    @SuppressWarnings("serial")
-    private static class IllegalAttribute extends ParseException {
-        public IllegalAttribute(XmlPullParser parser, String attribute) {
-            super("Tag " + parser.getName() + " has illegal attribute " + attribute, parser);
-        }
-    }
-
-    @SuppressWarnings("serial")
-    private static class NonEmptyTag extends ParseException {
-        public NonEmptyTag(String tag, XmlPullParser parser) {
-            super(tag + " must be empty tag", parser);
-        }
-    }
-
-    private static String textAttr(String value, String name) {
-        return value != null ? String.format(" %s=%s", name, value) : "";
-    }
-
-    private static String booleanAttr(TypedArray a, int index, String name) {
-        return a.hasValue(index) ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index faa5f86..ded89b1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -23,86 +23,94 @@
 
 import com.android.inputmethod.latin.R;
 
+import java.util.HashMap;
+
 public class KeyboardIconsSet {
     private static final String TAG = KeyboardIconsSet.class.getSimpleName();
 
+    // The value should be aligned with the enum value of Key.keyIcon.
     public static final int ICON_UNDEFINED = 0;
+    private static final int NUM_ICONS = 16;
 
-    // This should be aligned with Keyboard.keyIcon enum.
-    private static final int ICON_SHIFT_KEY = 1;
-    private static final int ICON_DELETE_KEY = 2;
-    private static final int ICON_SETTINGS_KEY = 3; // This is also represented as "@icon/3" in XML.
-    private static final int ICON_SPACE_KEY = 4;
-    private static final int ICON_RETURN_KEY = 5;
-    private static final int ICON_SEARCH_KEY = 6;
-    private static final int ICON_TAB_KEY = 7; // This is also represented as "@icon/7" in XML.
-    private static final int ICON_SHORTCUT_KEY = 8;
-    private static final int ICON_SHORTCUT_FOR_LABEL = 9;
-    // This should be aligned with Keyboard.keyIconShifted enum.
-    private static final int ICON_SHIFTED_SHIFT_KEY = 10;
-    // This should be aligned with Keyboard.keyIconPreview enum.
-    private static final int ICON_PREVIEW_TAB_KEY = 11;
+    private final Drawable[] mIcons = new Drawable[NUM_ICONS + 1];
 
-    private static final int ICON_LAST = 11;
+    private static final HashMap<Integer, Integer> ATTR_ID_TO_ICON_ID
+            = new HashMap<Integer, Integer>();
+    private static final HashMap<String, Integer> NAME_TO_ICON_ID = new HashMap<String, Integer>();
+    private static final String[] ICON_NAMES = new String[NUM_ICONS + 1];
 
-    private final Drawable mIcons[] = new Drawable[ICON_LAST + 1];
+    private static final int ATTR_UNDEFINED = 0;
+    static {
+        // The key value should be aligned with the enum value of Key.keyIcon.
+        addIconIdMap(0, "undefined", ATTR_UNDEFINED);
+        addIconIdMap(1, "shiftKey", R.styleable.Keyboard_iconShiftKey);
+        addIconIdMap(2, "deleteKey", R.styleable.Keyboard_iconDeleteKey);
+        addIconIdMap(3, "settingsKey", R.styleable.Keyboard_iconSettingsKey);
+        addIconIdMap(4, "spaceKey", R.styleable.Keyboard_iconSpaceKey);
+        addIconIdMap(5, "returnKey", R.styleable.Keyboard_iconReturnKey);
+        addIconIdMap(6, "searchKey", R.styleable.Keyboard_iconSearchKey);
+        addIconIdMap(7, "tabKey", R.styleable.Keyboard_iconTabKey);
+        addIconIdMap(8, "shortcutKey", R.styleable.Keyboard_iconShortcutKey);
+        addIconIdMap(9, "shortcutForLabel", R.styleable.Keyboard_iconShortcutForLabel);
+        addIconIdMap(10, "spaceKeyForNumberLayout",
+                R.styleable.Keyboard_iconSpaceKeyForNumberLayout);
+        addIconIdMap(11, "shiftKeyShifted", R.styleable.Keyboard_iconShiftKeyShifted);
+        addIconIdMap(12, "disabledShortcurKey", R.styleable.Keyboard_iconDisabledShortcutKey);
+        addIconIdMap(13, "previewTabKey", R.styleable.Keyboard_iconPreviewTabKey);
+        addIconIdMap(14, "languageSwitchKey", R.styleable.Keyboard_iconLanguageSwitchKey);
+        addIconIdMap(15, "zwnjKey", R.styleable.Keyboard_iconZwnjKey);
+        addIconIdMap(16, "zwjKey", R.styleable.Keyboard_iconZwjKey);
+    }
 
-    private static final int getIconId(final int attrIndex) {
-        switch (attrIndex) {
-        case R.styleable.Keyboard_iconShiftKey:
-            return ICON_SHIFT_KEY;
-        case R.styleable.Keyboard_iconDeleteKey:
-            return ICON_DELETE_KEY;
-        case R.styleable.Keyboard_iconSettingsKey:
-            return ICON_SETTINGS_KEY;
-        case R.styleable.Keyboard_iconSpaceKey:
-            return ICON_SPACE_KEY;
-        case R.styleable.Keyboard_iconReturnKey:
-            return ICON_RETURN_KEY;
-        case R.styleable.Keyboard_iconSearchKey:
-            return ICON_SEARCH_KEY;
-        case R.styleable.Keyboard_iconTabKey:
-            return ICON_TAB_KEY;
-        case R.styleable.Keyboard_iconShortcutKey:
-            return ICON_SHORTCUT_KEY;
-        case R.styleable.Keyboard_iconShortcutForLabel:
-            return ICON_SHORTCUT_FOR_LABEL;
-        case R.styleable.Keyboard_iconShiftedShiftKey:
-            return ICON_SHIFTED_SHIFT_KEY;
-        case R.styleable.Keyboard_iconPreviewTabKey:
-            return ICON_PREVIEW_TAB_KEY;
-        default:
-            return ICON_UNDEFINED;
+    private static void addIconIdMap(int iconId, String name, int attrId) {
+        if (attrId != ATTR_UNDEFINED) {
+            ATTR_ID_TO_ICON_ID.put(attrId,  iconId);
         }
+        NAME_TO_ICON_ID.put(name, iconId);
+        ICON_NAMES[iconId] = name;
     }
 
     public void loadIcons(final TypedArray keyboardAttrs) {
-        final int count = keyboardAttrs.getIndexCount();
-        for (int i = 0; i < count; i++) {
-            final int attrIndex = keyboardAttrs.getIndex(i);
-            final int iconId = getIconId(attrIndex);
-            if (iconId != ICON_UNDEFINED) {
-                try {
-                    mIcons[iconId] = setDefaultBounds(keyboardAttrs.getDrawable(attrIndex));
-                } catch (Resources.NotFoundException e) {
-                    Log.w(TAG, "Drawable resource for icon #" + iconId + " not found");
-                }
+        for (final Integer attrId : ATTR_ID_TO_ICON_ID.keySet()) {
+            try {
+                final Drawable icon = keyboardAttrs.getDrawable(attrId);
+                setDefaultBounds(icon);
+                final Integer iconId = ATTR_ID_TO_ICON_ID.get(attrId);
+                mIcons[iconId] = icon;
+            } catch (Resources.NotFoundException e) {
+                Log.w(TAG, "Drawable resource for icon #"
+                        + keyboardAttrs.getResources().getResourceEntryName(attrId)
+                        + " not found");
             }
         }
     }
 
-    public Drawable getIcon(final int iconId) {
-        if (iconId == ICON_UNDEFINED)
-            return null;
-        if (iconId < 0 || iconId >= mIcons.length)
-            throw new IllegalArgumentException("icon id is out of range: " + iconId);
-        return mIcons[iconId];
+    private static boolean isValidIconId(final int iconId) {
+        return iconId >= 0 && iconId < ICON_NAMES.length;
     }
 
-    private static Drawable setDefaultBounds(final Drawable icon)  {
+    public static String getIconName(final int iconId) {
+        return isValidIconId(iconId) ? ICON_NAMES[iconId] : "unknown<" + iconId + ">";
+    }
+
+    public static int getIconId(final String name) {
+        final Integer iconId = NAME_TO_ICON_ID.get(name);
+        if (iconId != null) {
+            return iconId;
+        }
+        throw new RuntimeException("unknown icon name: " + name);
+    }
+
+    public Drawable getIconDrawable(final int iconId) {
+        if (isValidIconId(iconId)) {
+            return mIcons[iconId];
+        }
+        throw new RuntimeException("unknown icon id: " + getIconName(iconId));
+    }
+
+    private static void setDefaultBounds(final Drawable icon)  {
         if (icon != null) {
             icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
         }
-        return icon;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
deleted file mode 100644
index 64cd37c..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.keyboard.internal;
-
-import android.graphics.drawable.Drawable;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.LatinImeLogger;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class KeyboardParams {
-    public KeyboardId mId;
-    public int mThemeId;
-
-    /** Total height and width of the keyboard, including the paddings and keys */
-    public int mOccupiedHeight;
-    public int mOccupiedWidth;
-
-    /** Base height and width of the keyboard used to calculate rows' or keys' heights and widths */
-    public int mBaseHeight;
-    public int mBaseWidth;
-
-    public int mTopPadding;
-    public int mBottomPadding;
-    public int mHorizontalEdgesPadding;
-    public int mHorizontalCenterPadding;
-
-    public int mDefaultRowHeight;
-    public int mDefaultKeyWidth;
-    public int mHorizontalGap;
-    public int mVerticalGap;
-
-    public boolean mIsRtlKeyboard;
-    public int mMoreKeysTemplate;
-    public int mMaxMiniKeyboardColumn;
-
-    public int GRID_WIDTH;
-    public int GRID_HEIGHT;
-
-    public final List<Key> mKeys = new ArrayList<Key>();
-    public final List<Key> mShiftKeys = new ArrayList<Key>();
-    public final Set<Key> mShiftLockKeys = new HashSet<Key>();
-    public final Map<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
-    public final Map<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>();
-    public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
-
-    public int mMostCommonKeyHeight = 0;
-    public int mMostCommonKeyWidth = 0;
-
-    public final TouchPositionCorrection mTouchPositionCorrection = new TouchPositionCorrection();
-
-    public static class TouchPositionCorrection {
-        private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
-
-        public boolean mEnabled;
-        public float[] mXs;
-        public float[] mYs;
-        public float[] mRadii;
-
-        public void load(String[] data) {
-            final int dataLength = data.length;
-            if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
-                if (LatinImeLogger.sDBG)
-                    throw new RuntimeException(
-                            "the size of touch position correction data is invalid");
-                return;
-            }
-
-            final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
-            mXs = new float[length];
-            mYs = new float[length];
-            mRadii = new float[length];
-            try {
-                for (int i = 0; i < dataLength; ++i) {
-                    final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
-                    final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
-                    final float value = Float.parseFloat(data[i]);
-                    if (type == 0) {
-                        mXs[index] = value;
-                    } else if (type == 1) {
-                        mYs[index] = value;
-                    } else {
-                        mRadii[index] = value;
-                    }
-                }
-            } catch (NumberFormatException e) {
-                if (LatinImeLogger.sDBG) {
-                    throw new RuntimeException(
-                            "the number format for touch position correction data is invalid");
-                }
-                mXs = null;
-                mYs = null;
-                mRadii = null;
-            }
-        }
-
-        public void setEnabled(boolean enabled) {
-            mEnabled = enabled;
-        }
-
-        public boolean isValid() {
-            return mEnabled && mXs != null && mYs != null && mRadii != null
-                && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
-        }
-    }
-
-    protected void clearKeys() {
-        mKeys.clear();
-        mShiftKeys.clear();
-        mShiftLockKeys.clear();
-        mShiftedIcons.clear();
-        mUnshiftedIcons.clear();
-        clearHistogram();
-    }
-
-    public void onAddKey(Key key) {
-        mKeys.add(key);
-        updateHistogram(key);
-        if (key.mCode == Keyboard.CODE_SHIFT) {
-            mShiftKeys.add(key);
-            if (key.isSticky()) {
-                mShiftLockKeys.add(key);
-            }
-        }
-    }
-
-    public void addShiftedIcon(Key key, Drawable icon) {
-        mUnshiftedIcons.put(key, key.getIcon());
-        mShiftedIcons.put(key, icon);
-    }
-
-    private int mMaxHeightCount = 0;
-    private int mMaxWidthCount = 0;
-    private final Map<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>();
-    private final Map<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>();
-
-    private void clearHistogram() {
-        mMostCommonKeyHeight = 0;
-        mMaxHeightCount = 0;
-        mHeightHistogram.clear();
-
-        mMaxWidthCount = 0;
-        mMostCommonKeyWidth = 0;
-        mWidthHistogram.clear();
-    }
-
-    private static int updateHistogramCounter(Map<Integer, Integer> histogram, Integer key) {
-        final int count = (histogram.containsKey(key) ? histogram.get(key) : 0) + 1;
-        histogram.put(key, count);
-        return count;
-    }
-
-    private void updateHistogram(Key key) {
-        final Integer height = key.mHeight + key.mVerticalGap;
-        final int heightCount = updateHistogramCounter(mHeightHistogram, height);
-        if (heightCount > mMaxHeightCount) {
-            mMaxHeightCount = heightCount;
-            mMostCommonKeyHeight = height;
-        }
-
-        final Integer width = key.mWidth + key.mHorizontalGap;
-        final int widthCount = updateHistogramCounter(mWidthHistogram, width);
-        if (widthCount > mMaxWidthCount) {
-            mMaxWidthCount = widthCount;
-            mMostCommonKeyWidth = width;
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
new file mode 100644
index 0000000..18a3f97
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+/**
+ * Keyboard state machine.
+ *
+ * This class contains all keyboard state transition logic.
+ *
+ * The input events are {@link #onLoadKeyboard(String)}, {@link #onSaveKeyboardState()},
+ * {@link #onPressKey(int)}, {@link #onReleaseKey(int, boolean)},
+ * {@link #onCodeInput(int, boolean, boolean)}, {@link #onCancelInput(boolean)},
+ * {@link #onUpdateShiftState(boolean)}, {@link #onLongPressTimeout(int)}.
+ *
+ * The actions are {@link SwitchActions}'s methods.
+ */
+public class KeyboardState {
+    private static final String TAG = KeyboardState.class.getSimpleName();
+    private static final boolean DEBUG_EVENT = false;
+    private static final boolean DEBUG_ACTION = false;
+
+    public interface SwitchActions {
+        public void setAlphabetKeyboard();
+        public void setAlphabetManualShiftedKeyboard();
+        public void setAlphabetAutomaticShiftedKeyboard();
+        public void setAlphabetShiftLockedKeyboard();
+        public void setAlphabetShiftLockShiftedKeyboard();
+        public void setSymbolsKeyboard();
+        public void setSymbolsShiftedKeyboard();
+
+        /**
+         * Request to call back {@link KeyboardState#onUpdateShiftState(boolean)}.
+         */
+        public void requestUpdatingShiftState();
+
+        public void startDoubleTapTimer();
+        public boolean isInDoubleTapTimeout();
+        public void cancelDoubleTapTimer();
+        public void startLongPressTimer(int code);
+        public void cancelLongPressTimer();
+        public void hapticAndAudioFeedback(int code);
+    }
+
+    private final SwitchActions mSwitchActions;
+
+    private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
+    private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
+
+    // TODO: Merge {@link #mSwitchState}, {@link #mIsAlphabetMode}, {@link #mAlphabetShiftState},
+    // {@link #mIsSymbolShifted}, {@link #mPrevMainKeyboardWasShiftLocked}, and
+    // {@link #mPrevSymbolsKeyboardWasShifted} into single state variable.
+    private static final int SWITCH_STATE_ALPHA = 0;
+    private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
+    private static final int SWITCH_STATE_SYMBOL = 2;
+    private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
+    private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
+    private int mSwitchState = SWITCH_STATE_ALPHA;
+    private String mLayoutSwitchBackSymbols;
+
+    private boolean mIsAlphabetMode;
+    private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState();
+    private boolean mIsSymbolShifted;
+    private boolean mPrevMainKeyboardWasShiftLocked;
+    private boolean mPrevSymbolsKeyboardWasShifted;
+
+    // For handling double tap.
+    private boolean mIsInAlphabetUnshiftedFromShifted;
+    private boolean mIsInDoubleTapShiftKey;
+
+    private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
+
+    static class SavedKeyboardState {
+        public boolean mIsValid;
+        public boolean mIsAlphabetMode;
+        public boolean mIsAlphabetShiftLocked;
+        public boolean mIsShifted;
+
+        @Override
+        public String toString() {
+            if (!mIsValid) return "INVALID";
+            if (mIsAlphabetMode) {
+                if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED";
+                return mIsShifted ? "ALPHABET_SHIFTED" : "ALPHABET";
+            } else {
+                return mIsShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS";
+            }
+        }
+    }
+
+    public KeyboardState(SwitchActions switchActions) {
+        mSwitchActions = switchActions;
+    }
+
+    public void onLoadKeyboard(String layoutSwitchBackSymbols) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onLoadKeyboard: " + this);
+        }
+        mLayoutSwitchBackSymbols = layoutSwitchBackSymbols;
+        // Reset alphabet shift state.
+        mAlphabetShiftState.setShiftLocked(false);
+        mPrevMainKeyboardWasShiftLocked = false;
+        mPrevSymbolsKeyboardWasShifted = false;
+        mShiftKeyState.onRelease();
+        mSymbolKeyState.onRelease();
+        onRestoreKeyboardState();
+    }
+
+    public void onSaveKeyboardState() {
+        final SavedKeyboardState state = mSavedKeyboardState;
+        state.mIsAlphabetMode = mIsAlphabetMode;
+        if (mIsAlphabetMode) {
+            state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked();
+            state.mIsShifted = !state.mIsAlphabetShiftLocked
+                    && mAlphabetShiftState.isShiftedOrShiftLocked();
+        } else {
+            state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked;
+            state.mIsShifted = mIsSymbolShifted;
+        }
+        state.mIsValid = true;
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onSaveKeyboardState: saved=" + state + " " + this);
+        }
+    }
+
+    private void onRestoreKeyboardState() {
+        final SavedKeyboardState state = mSavedKeyboardState;
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this);
+        }
+        if (!state.mIsValid || state.mIsAlphabetMode) {
+            setAlphabetKeyboard();
+        } else {
+            if (state.mIsShifted) {
+                setSymbolsShiftedKeyboard();
+            } else {
+                setSymbolsKeyboard();
+            }
+        }
+
+        if (!state.mIsValid) return;
+        state.mIsValid = false;
+
+        if (state.mIsAlphabetMode) {
+            setShiftLocked(state.mIsAlphabetShiftLocked);
+            if (!state.mIsAlphabetShiftLocked) {
+                setShifted(state.mIsShifted ? MANUAL_SHIFT : UNSHIFT);
+            }
+        } else {
+            mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
+        }
+    }
+
+    private static final int UNSHIFT = 0;
+    private static final int MANUAL_SHIFT = 1;
+    private static final int AUTOMATIC_SHIFT = 2;
+    private static final int SHIFT_LOCK_SHIFTED = 3;
+
+    private void setShifted(int shiftMode) {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
+        }
+        if (!mIsAlphabetMode) return;
+        final int prevShiftMode;
+        if (mAlphabetShiftState.isAutomaticShifted()) {
+            prevShiftMode = AUTOMATIC_SHIFT;
+        } else if (mAlphabetShiftState.isManualShifted()) {
+            prevShiftMode = MANUAL_SHIFT;
+        } else {
+            prevShiftMode = UNSHIFT;
+        }
+        switch (shiftMode) {
+        case AUTOMATIC_SHIFT:
+            mAlphabetShiftState.setAutomaticShifted();
+            if (shiftMode != prevShiftMode) {
+                mSwitchActions.setAlphabetAutomaticShiftedKeyboard();
+            }
+            break;
+        case MANUAL_SHIFT:
+            mAlphabetShiftState.setShifted(true);
+            if (shiftMode != prevShiftMode) {
+                mSwitchActions.setAlphabetManualShiftedKeyboard();
+            }
+            break;
+        case UNSHIFT:
+            mAlphabetShiftState.setShifted(false);
+            if (shiftMode != prevShiftMode) {
+                mSwitchActions.setAlphabetKeyboard();
+            }
+            break;
+        case SHIFT_LOCK_SHIFTED:
+            mAlphabetShiftState.setShifted(true);
+            mSwitchActions.setAlphabetShiftLockShiftedKeyboard();
+            break;
+        }
+    }
+
+    private void setShiftLocked(boolean shiftLocked) {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
+        }
+        if (!mIsAlphabetMode) return;
+        if (shiftLocked && (!mAlphabetShiftState.isShiftLocked()
+                || mAlphabetShiftState.isShiftLockShifted())) {
+            mSwitchActions.setAlphabetShiftLockedKeyboard();
+        }
+        if (!shiftLocked && mAlphabetShiftState.isShiftLocked()) {
+            mSwitchActions.setAlphabetKeyboard();
+        }
+        mAlphabetShiftState.setShiftLocked(shiftLocked);
+    }
+
+    private void toggleAlphabetAndSymbols() {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "toggleAlphabetAndSymbols: " + this);
+        }
+        if (mIsAlphabetMode) {
+            mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
+            if (mPrevSymbolsKeyboardWasShifted) {
+                setSymbolsShiftedKeyboard();
+            } else {
+                setSymbolsKeyboard();
+            }
+            mPrevSymbolsKeyboardWasShifted = false;
+        } else {
+            mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
+            setAlphabetKeyboard();
+            if (mPrevMainKeyboardWasShiftLocked) {
+                setShiftLocked(true);
+            }
+            mPrevMainKeyboardWasShiftLocked = false;
+        }
+    }
+
+    private void toggleShiftInSymbols() {
+        if (mIsSymbolShifted) {
+            setSymbolsKeyboard();
+        } else {
+            setSymbolsShiftedKeyboard();
+        }
+    }
+
+    private void setAlphabetKeyboard() {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setAlphabetKeyboard");
+        }
+        mSwitchActions.setAlphabetKeyboard();
+        mIsAlphabetMode = true;
+        mIsSymbolShifted = false;
+        mSwitchState = SWITCH_STATE_ALPHA;
+        mSwitchActions.requestUpdatingShiftState();
+    }
+
+    private void setSymbolsKeyboard() {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setSymbolsKeyboard");
+        }
+        mSwitchActions.setSymbolsKeyboard();
+        mIsAlphabetMode = false;
+        mIsSymbolShifted = false;
+        // Reset alphabet shift state.
+        mAlphabetShiftState.setShiftLocked(false);
+        mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+    }
+
+    private void setSymbolsShiftedKeyboard() {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setSymbolsShiftedKeyboard");
+        }
+        mSwitchActions.setSymbolsShiftedKeyboard();
+        mIsAlphabetMode = false;
+        mIsSymbolShifted = true;
+        // Reset alphabet shift state.
+        mAlphabetShiftState.setShiftLocked(false);
+        mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+    }
+
+    public void onPressKey(int code) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onPressKey: code=" + Keyboard.printableCode(code) + " " + this);
+        }
+        if (code == Keyboard.CODE_SHIFT) {
+            onPressShift();
+        } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+            onPressSymbol();
+        } else {
+            mSwitchActions.cancelDoubleTapTimer();
+            mSwitchActions.cancelLongPressTimer();
+            mShiftKeyState.onOtherKeyPressed();
+            mSymbolKeyState.onOtherKeyPressed();
+        }
+    }
+
+    public void onReleaseKey(int code, boolean withSliding) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onReleaseKey: code=" + Keyboard.printableCode(code)
+                    + " sliding=" + withSliding + " " + this);
+        }
+        if (code == Keyboard.CODE_SHIFT) {
+            onReleaseShift(withSliding);
+        } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+            onReleaseSymbol(withSliding);
+        }
+    }
+
+    private void onPressSymbol() {
+        toggleAlphabetAndSymbols();
+        mSymbolKeyState.onPress();
+        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
+    }
+
+    private void onReleaseSymbol(boolean withSliding) {
+        if (mSymbolKeyState.isChording()) {
+            // Switch back to the previous keyboard mode if the user chords the mode change key and
+            // another key, then releases the mode change key.
+            toggleAlphabetAndSymbols();
+        } else if (!withSliding) {
+            // If the mode change key is being released without sliding, we should forget the
+            // previous symbols keyboard shift state and simply switch back to symbols layout
+            // (never symbols shifted) next time the mode gets changed to symbols layout.
+            mPrevSymbolsKeyboardWasShifted = false;
+        }
+        mSymbolKeyState.onRelease();
+    }
+
+    public void onLongPressTimeout(int code) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " " + this);
+        }
+        if (mIsAlphabetMode && code == Keyboard.CODE_SHIFT) {
+            if (mAlphabetShiftState.isShiftLocked()) {
+                setShiftLocked(false);
+                // Shift key is long pressed while shift locked state, we will toggle back to normal
+                // state. And mark as if shift key is released.
+                mShiftKeyState.onRelease();
+            } else {
+                // Shift key is long pressed while shift unlocked state.
+                setShiftLocked(true);
+            }
+            mSwitchActions.hapticAndAudioFeedback(code);
+        }
+    }
+
+    public void onUpdateShiftState(boolean autoCaps) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
+        }
+        updateAlphabetShiftState(autoCaps);
+    }
+
+    private void updateAlphabetShiftState(boolean autoCaps) {
+        if (!mIsAlphabetMode) return;
+        if (!mShiftKeyState.isReleasing()) {
+            // Ignore update shift state event while the shift key is being pressed (including
+            // chording).
+            return;
+        }
+        if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) {
+            if (mShiftKeyState.isReleasing() && autoCaps) {
+                // Only when shift key is releasing, automatic temporary upper case will be set.
+                setShifted(AUTOMATIC_SHIFT);
+            } else {
+                setShifted(mShiftKeyState.isChording() ? MANUAL_SHIFT : UNSHIFT);
+            }
+        }
+    }
+
+    private void onPressShift() {
+        if (mIsAlphabetMode) {
+            mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout();
+            if (!mIsInDoubleTapShiftKey) {
+                // This is first tap.
+                mSwitchActions.startDoubleTapTimer();
+            }
+            if (mIsInDoubleTapShiftKey) {
+                if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
+                    // Shift key has been double tapped while in manual shifted or automatic
+                    // shifted state.
+                    setShiftLocked(true);
+                } else {
+                    // Shift key has been double tapped while in normal state. This is the second
+                    // tap to disable shift locked state, so just ignore this.
+                }
+            } else {
+                if (mAlphabetShiftState.isShiftLocked()) {
+                    // Shift key is pressed while shift locked state, we will treat this state as
+                    // shift lock shifted state and mark as if shift key pressed while normal state.
+                    setShifted(SHIFT_LOCK_SHIFTED);
+                    mShiftKeyState.onPress();
+                } else if (mAlphabetShiftState.isAutomaticShifted()) {
+                    // Shift key is pressed while automatic shifted, we have to move to manual
+                    // shifted.
+                    setShifted(MANUAL_SHIFT);
+                    mShiftKeyState.onPress();
+                } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
+                    // In manual shifted state, we just record shift key has been pressing while
+                    // shifted state.
+                    mShiftKeyState.onPressOnShifted();
+                } else {
+                    // In base layout, chording or manual shifted mode is started.
+                    setShifted(MANUAL_SHIFT);
+                    mShiftKeyState.onPress();
+                }
+                mSwitchActions.startLongPressTimer(Keyboard.CODE_SHIFT);
+            }
+        } else {
+            // In symbol mode, just toggle symbol and symbol more keyboard.
+            toggleShiftInSymbols();
+            mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+            mShiftKeyState.onPress();
+        }
+    }
+
+    private void onReleaseShift(boolean withSliding) {
+        if (mIsAlphabetMode) {
+            final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
+            mIsInAlphabetUnshiftedFromShifted = false;
+            if (mIsInDoubleTapShiftKey) {
+                // Double tap shift key has been handled in {@link #onPressShift}, so that just
+                // ignore this release shift key here.
+                mIsInDoubleTapShiftKey = false;
+            } else if (mShiftKeyState.isChording()) {
+                if (mAlphabetShiftState.isShiftLockShifted()) {
+                    // After chording input while shift locked state.
+                    setShiftLocked(true);
+                } else {
+                    // After chording input while normal state.
+                    setShifted(UNSHIFT);
+                }
+            } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) {
+                // In shift locked state, shift has been pressed and slid out to other key.
+                setShiftLocked(true);
+            } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted()
+                    && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted())
+                    && !withSliding) {
+                // Shift has been long pressed, ignore this release.
+            } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) {
+                // Shift has been pressed without chording while shift locked state.
+                setShiftLocked(false);
+            } else if (mAlphabetShiftState.isShiftedOrShiftLocked()
+                    && mShiftKeyState.isPressingOnShifted() && !withSliding) {
+                // Shift has been pressed without chording while shifted state.
+                setShifted(UNSHIFT);
+                mIsInAlphabetUnshiftedFromShifted = true;
+            } else if (mAlphabetShiftState.isManualShiftedFromAutomaticShifted()
+                    && mShiftKeyState.isPressing() && !withSliding) {
+                // Shift has been pressed without chording while manual shifted transited from
+                // automatic shifted
+                setShifted(UNSHIFT);
+                mIsInAlphabetUnshiftedFromShifted = true;
+            }
+        } else {
+            // In symbol mode, switch back to the previous keyboard mode if the user chords the
+            // shift key and another key, then releases the shift key.
+            if (mShiftKeyState.isChording()) {
+                toggleShiftInSymbols();
+            }
+        }
+        mShiftKeyState.onRelease();
+    }
+
+    public void onCancelInput(boolean isSinglePointer) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this);
+        }
+        // Switch back to the previous keyboard mode if the user cancels sliding input.
+        if (isSinglePointer) {
+            if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
+                toggleAlphabetAndSymbols();
+            } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
+                toggleShiftInSymbols();
+            }
+        }
+    }
+
+    public boolean isInMomentarySwitchState() {
+        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
+                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+    }
+
+    private static boolean isSpaceCharacter(int c) {
+        return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
+    }
+
+    private boolean isLayoutSwitchBackCharacter(int c) {
+        if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
+        if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
+        return false;
+    }
+
+    public void onCodeInput(int code, boolean isSinglePointer, boolean autoCaps) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onCodeInput: code=" + Keyboard.printableCode(code)
+                    + " single=" + isSinglePointer
+                    + " autoCaps=" + autoCaps + " " + this);
+        }
+
+        switch (mSwitchState) {
+        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
+            if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+                // Detected only the mode change key has been pressed, and then released.
+                if (mIsAlphabetMode) {
+                    mSwitchState = SWITCH_STATE_ALPHA;
+                } else {
+                    mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+                }
+            } else if (isSinglePointer) {
+                // Switch back to the previous keyboard mode if the user pressed the mode change key
+                // and slid to other key, then released the finger.
+                // If the user cancels the sliding input, switching back to the previous keyboard
+                // mode is handled by {@link #onCancelInput}.
+                toggleAlphabetAndSymbols();
+            }
+            break;
+        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
+            if (code == Keyboard.CODE_SHIFT) {
+                // Detected only the shift key has been pressed on symbol layout, and then released.
+                mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+            } else if (isSinglePointer) {
+                // Switch back to the previous keyboard mode if the user pressed the shift key on
+                // symbol mode and slid to other key, then released the finger.
+                toggleShiftInSymbols();
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            }
+            break;
+        case SWITCH_STATE_SYMBOL_BEGIN:
+            if (!isSpaceCharacter(code) && (Keyboard.isLetterCode(code)
+                    || code == Keyboard.CODE_OUTPUT_TEXT)) {
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            }
+            // Switch back to alpha keyboard mode immediately if user types one of the switch back
+            // characters.
+            if (isLayoutSwitchBackCharacter(code)) {
+                toggleAlphabetAndSymbols();
+                mPrevSymbolsKeyboardWasShifted = false;
+            }
+            break;
+        case SWITCH_STATE_SYMBOL:
+            // Switch back to alpha keyboard mode if user types one or more non-space/enter
+            // characters followed by a space/enter or one of the switch back characters.
+            if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
+                toggleAlphabetAndSymbols();
+                mPrevSymbolsKeyboardWasShifted = false;
+            }
+            break;
+        }
+
+        // If the code is a letter, update keyboard shift state.
+        if (Keyboard.isLetterCode(code)) {
+            updateAlphabetShiftState(autoCaps);
+        }
+    }
+
+    private static String shiftModeToString(int shiftMode) {
+        switch (shiftMode) {
+        case UNSHIFT: return "UNSHIFT";
+        case MANUAL_SHIFT: return "MANUAL";
+        case AUTOMATIC_SHIFT: return "AUTOMATIC";
+        default: return null;
+        }
+    }
+
+    private static String switchStateToString(int switchState) {
+        switch (switchState) {
+        case SWITCH_STATE_ALPHA: return "ALPHA";
+        case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
+        case SWITCH_STATE_SYMBOL: return "SYMBOL";
+        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL";
+        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE";
+        default: return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString()
+                        : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS"))
+                + " shift=" + mShiftKeyState
+                + " symbol=" + mSymbolKeyState
+                + " switch=" + switchStateToString(mSwitchState) + "]";
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
index dae73c4..b39b977 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
@@ -18,15 +18,13 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-
-public class ModifierKeyState {
-    protected static final String TAG = "ModifierKeyState";
-    protected static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
+/* package */ class ModifierKeyState {
+    protected static final String TAG = ModifierKeyState.class.getSimpleName();
+    protected static final boolean DEBUG = false;
 
     protected static final int RELEASING = 0;
     protected static final int PRESSING = 1;
-    protected static final int MOMENTARY = 2;
+    protected static final int CHORDING = 2;
 
     protected final String mName;
     protected int mState = RELEASING;
@@ -52,7 +50,7 @@
     public void onOtherKeyPressed() {
         final int oldState = mState;
         if (oldState == PRESSING)
-            mState = MOMENTARY;
+            mState = CHORDING;
         if (DEBUG)
             Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this);
     }
@@ -65,8 +63,8 @@
         return mState == RELEASING;
     }
 
-    public boolean isMomentary() {
-        return mState == MOMENTARY;
+    public boolean isChording() {
+        return mState == CHORDING;
     }
 
     @Override
@@ -78,7 +76,7 @@
         switch (state) {
         case RELEASING: return "RELEASING";
         case PRESSING: return "PRESSING";
-        case MOMENTARY: return "MOMENTARY";
+        case CHORDING: return "CHORDING";
         default: return "UNKNOWN";
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java
deleted file mode 100644
index a490b0a..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * 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.keyboard.internal;
-
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.R;
-
-import java.util.ArrayList;
-
-/**
- * String parser of moreKeys attribute of Key.
- * The string is comma separated texts each of which represents one "more key".
- * Each "more key" specification is one of the following:
- * - A single letter (Letter)
- * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
- * - Icon followed by keyOutputText or code (@icon/icon_number|@integer/key_code)
- * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\'
- * character.
- * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well.
- * See {@link KeyboardIconsSet} about icon_number.
- */
-public class MoreKeySpecParser {
-    private static final String TAG = MoreKeySpecParser.class.getSimpleName();
-
-    private static final char ESCAPE = '\\';
-    private static final String LABEL_END = "|";
-    private static final String PREFIX_AT = "@";
-    private static final String PREFIX_ICON = PREFIX_AT + "icon/";
-    private static final String PREFIX_CODE = PREFIX_AT + "integer/";
-
-    private MoreKeySpecParser() {
-        // Intentional empty constructor for utility class.
-    }
-
-    private static boolean hasIcon(String moreKeySpec) {
-        if (moreKeySpec.startsWith(PREFIX_ICON)) {
-            final int end = indexOfLabelEnd(moreKeySpec, 0);
-            if (end > 0)
-                return true;
-            throw new MoreKeySpecParserError("outputText or code not specified: " + moreKeySpec);
-        }
-        return false;
-    }
-
-    private static boolean hasCode(String moreKeySpec) {
-        final int end = indexOfLabelEnd(moreKeySpec, 0);
-        if (end > 0 && end + 1 < moreKeySpec.length()
-                && moreKeySpec.substring(end + 1).startsWith(PREFIX_CODE)) {
-            return true;
-        }
-        return false;
-    }
-
-    private static String parseEscape(String text) {
-        if (text.indexOf(ESCAPE) < 0)
-            return text;
-        final int length = text.length();
-        final StringBuilder sb = new StringBuilder();
-        for (int pos = 0; pos < length; pos++) {
-            final char c = text.charAt(pos);
-            if (c == ESCAPE && pos + 1 < length) {
-                sb.append(text.charAt(++pos));
-            } else {
-                sb.append(c);
-            }
-        }
-        return sb.toString();
-    }
-
-    private static int indexOfLabelEnd(String moreKeySpec, int start) {
-        if (moreKeySpec.indexOf(ESCAPE, start) < 0) {
-            final int end = moreKeySpec.indexOf(LABEL_END, start);
-            if (end == 0)
-                throw new MoreKeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
-            return end;
-        }
-        final int length = moreKeySpec.length();
-        for (int pos = start; pos < length; pos++) {
-            final char c = moreKeySpec.charAt(pos);
-            if (c == ESCAPE && pos + 1 < length) {
-                pos++;
-            } else if (moreKeySpec.startsWith(LABEL_END, pos)) {
-                return pos;
-            }
-        }
-        return -1;
-    }
-
-    public static String getLabel(String moreKeySpec) {
-        if (hasIcon(moreKeySpec))
-            return null;
-        final int end = indexOfLabelEnd(moreKeySpec, 0);
-        final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end))
-                : parseEscape(moreKeySpec);
-        if (TextUtils.isEmpty(label))
-            throw new MoreKeySpecParserError("Empty label: " + moreKeySpec);
-        return label;
-    }
-
-    public static String getOutputText(String moreKeySpec) {
-        if (hasCode(moreKeySpec))
-            return null;
-        final int end = indexOfLabelEnd(moreKeySpec, 0);
-        if (end > 0) {
-            if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0)
-                    throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": "
-                            + moreKeySpec);
-            final String outputText = parseEscape(moreKeySpec.substring(end + LABEL_END.length()));
-            if (!TextUtils.isEmpty(outputText))
-                return outputText;
-            throw new MoreKeySpecParserError("Empty outputText: " + moreKeySpec);
-        }
-        final String label = getLabel(moreKeySpec);
-        if (label == null)
-            throw new MoreKeySpecParserError("Empty label: " + moreKeySpec);
-        // Code is automatically generated for one letter label. See {@link getCode()}.
-        if (label.length() == 1)
-            return null;
-        return label;
-    }
-
-    public static int getCode(Resources res, String moreKeySpec) {
-        if (hasCode(moreKeySpec)) {
-            final int end = indexOfLabelEnd(moreKeySpec, 0);
-            if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0)
-                throw new MoreKeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
-            final int resId = getResourceId(res,
-                    moreKeySpec.substring(end + LABEL_END.length() + PREFIX_AT.length()));
-            final int code = res.getInteger(resId);
-            return code;
-        }
-        if (indexOfLabelEnd(moreKeySpec, 0) > 0)
-            return Keyboard.CODE_DUMMY;
-        final String label = getLabel(moreKeySpec);
-        // Code is automatically generated for one letter label.
-        if (label != null && label.length() == 1)
-            return label.charAt(0);
-        return Keyboard.CODE_DUMMY;
-    }
-
-    public static int getIconId(String moreKeySpec) {
-        if (hasIcon(moreKeySpec)) {
-            int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1);
-            final String iconId = moreKeySpec.substring(PREFIX_ICON.length(), end);
-            try {
-                return Integer.valueOf(iconId);
-            } catch (NumberFormatException e) {
-                Log.w(TAG, "illegal icon id specified: " + iconId);
-                return KeyboardIconsSet.ICON_UNDEFINED;
-            }
-        }
-        return KeyboardIconsSet.ICON_UNDEFINED;
-    }
-
-    private static int getResourceId(Resources res, String name) {
-        String packageName = res.getResourcePackageName(R.string.english_ime_name);
-        int resId = res.getIdentifier(name, null, packageName);
-        if (resId == 0)
-            throw new MoreKeySpecParserError("Unknown resource: " + name);
-        return resId;
-    }
-
-    @SuppressWarnings("serial")
-    public static class MoreKeySpecParserError extends RuntimeException {
-        public MoreKeySpecParserError(String message) {
-            super(message);
-        }
-    }
-
-    public interface CodeFilter {
-        public boolean shouldFilterOut(int code);
-    }
-
-    public static final CodeFilter DIGIT_FILTER = new CodeFilter() {
-        @Override
-        public boolean shouldFilterOut(int code) {
-            return Character.isDigit(code);
-        }
-    };
-
-    public static CharSequence[] filterOut(Resources res, CharSequence[] moreKeys,
-            CodeFilter filter) {
-        if (moreKeys == null || moreKeys.length < 1) {
-            return null;
-        }
-        if (moreKeys.length == 1
-                && filter.shouldFilterOut(getCode(res, moreKeys[0].toString()))) {
-            return null;
-        }
-        ArrayList<CharSequence> filtered = null;
-        for (int i = 0; i < moreKeys.length; i++) {
-            final CharSequence moreKeySpec = moreKeys[i];
-            if (filter.shouldFilterOut(getCode(res, moreKeySpec.toString()))) {
-                if (filtered == null) {
-                    filtered = new ArrayList<CharSequence>();
-                    for (int j = 0; j < i; j++) {
-                        filtered.add(moreKeys[j]);
-                    }
-                }
-            } else if (filtered != null) {
-                filtered.add(moreKeySpec);
-            }
-        }
-        if (filtered == null) {
-            return moreKeys;
-        }
-        if (filtered.size() == 0) {
-            return null;
-        }
-        return filtered.toArray(new CharSequence[filtered.size()]);
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 08e7a7a..5db65c6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -16,29 +16,45 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.util.Log;
+
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.PointerTracker;
 
+import java.util.Iterator;
 import java.util.LinkedList;
 
 public class PointerTrackerQueue {
+    private static final String TAG = PointerTrackerQueue.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
     private final LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
 
     public synchronized void add(PointerTracker tracker) {
         mQueue.add(tracker);
     }
 
-    public synchronized void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) {
-        if (mQueue.lastIndexOf(tracker) < 0) {
+    public synchronized void remove(PointerTracker tracker) {
+        mQueue.remove(tracker);
+    }
+
+    public synchronized void releaseAllPointersOlderThan(PointerTracker tracker,
+            long eventTime) {
+        if (DEBUG) {
+            Log.d(TAG, "releaseAllPoniterOlderThan: [" + tracker.mPointerId + "] " + this);
+        }
+        if (!mQueue.contains(tracker)) {
             return;
         }
-        final LinkedList<PointerTracker> queue = mQueue;
-        int oldestPos = 0;
-        for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
-            if (t.isModifier()) {
-                oldestPos++;
-            } else {
+        final Iterator<PointerTracker> it = mQueue.iterator();
+        while (it.hasNext()) {
+            final PointerTracker t = it.next();
+            if (t == tracker) {
+                break;
+            }
+            if (!t.isModifier()) {
                 t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
-                queue.remove(oldestPos);
+                it.remove();
             }
         }
     }
@@ -48,22 +64,23 @@
     }
 
     public synchronized void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
-        for (PointerTracker t : mQueue) {
-            if (t == tracker) {
-                continue;
+        if (DEBUG) {
+            if (tracker == null) {
+                Log.d(TAG, "releaseAllPoniters: " + this);
+            } else {
+                Log.d(TAG, "releaseAllPoniterExcept: [" + tracker.mPointerId + "] " + this);
             }
-            t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
         }
-        mQueue.clear();
-        if (tracker != null) {
-            mQueue.add(tracker);
+        final Iterator<PointerTracker> it = mQueue.iterator();
+        while (it.hasNext()) {
+            final PointerTracker t = it.next();
+            if (t != tracker) {
+                t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
+                it.remove();
+            }
         }
     }
 
-    public synchronized void remove(PointerTracker tracker) {
-        mQueue.remove(tracker);
-    }
-
     public synchronized boolean isAnyInSlidingKeyInput() {
         for (final PointerTracker tracker : mQueue) {
             if (tracker.isInSlidingKeyInput()) {
@@ -75,13 +92,13 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder("[");
-        for (PointerTracker tracker : mQueue) {
-            if (sb.length() > 1)
+        final StringBuilder sb = new StringBuilder();
+        for (final PointerTracker tracker : mQueue) {
+            if (sb.length() > 0)
                 sb.append(" ");
-            sb.append(String.format("%d", tracker.mPointerId));
+            sb.append("[" + tracker.mPointerId + " "
+                + Keyboard.printableCode(tracker.getKey().mCode) + "]");
         }
-        sb.append("]");
         return sb.toString();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
index 6617b91..edb40c8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
@@ -18,7 +18,7 @@
 
 import android.util.Log;
 
-public class ShiftKeyState extends ModifierKeyState {
+/* package */ class ShiftKeyState extends ModifierKeyState {
     private static final int PRESSING_ON_SHIFTED = 3; // both temporary shifted & shift locked
     private static final int IGNORING = 4;
 
@@ -30,7 +30,7 @@
     public void onOtherKeyPressed() {
         int oldState = mState;
         if (oldState == PRESSING) {
-            mState = MOMENTARY;
+            mState = CHORDING;
         } else if (oldState == PRESSING_ON_SHIFTED) {
             mState = IGNORING;
         }
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
new file mode 100644
index 0000000..55664d4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.VibratorUtils;
+
+/**
+ * This class gathers audio feedback and haptic feedback functions.
+ *
+ * It offers a consistent and simple interface that allows LatinIME to forget about the
+ * complexity of settings and the like.
+ */
+public class AudioAndHapticFeedbackManager {
+    final private SettingsValues mSettingsValues;
+    final private AudioManager mAudioManager;
+    final private VibratorUtils mVibratorUtils;
+    private boolean mSoundOn;
+
+    public AudioAndHapticFeedbackManager(final LatinIME latinIme,
+            final SettingsValues settingsValues) {
+        mSettingsValues = settingsValues;
+        mVibratorUtils = VibratorUtils.getInstance(latinIme);
+        mAudioManager = (AudioManager) latinIme.getSystemService(Context.AUDIO_SERVICE);
+        mSoundOn = reevaluateIfSoundIsOn();
+    }
+
+    public void hapticAndAudioFeedback(final int primaryCode,
+            final View viewToPerformHapticFeedbackOn) {
+        vibrate(viewToPerformHapticFeedbackOn);
+        playKeyClick(primaryCode);
+    }
+
+    private boolean reevaluateIfSoundIsOn() {
+        if (!mSettingsValues.mSoundOn || mAudioManager == null) {
+            return false;
+        } else {
+            return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
+        }
+    }
+
+    private void playKeyClick(int primaryCode) {
+        // if mAudioManager is null, we can't play a sound anyway, so return
+        if (mAudioManager == null) return;
+        if (mSoundOn) {
+            final int sound;
+            switch (primaryCode) {
+            case Keyboard.CODE_DELETE:
+                sound = AudioManager.FX_KEYPRESS_DELETE;
+                break;
+            case Keyboard.CODE_ENTER:
+                sound = AudioManager.FX_KEYPRESS_RETURN;
+                break;
+            case Keyboard.CODE_SPACE:
+                sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+                break;
+            default:
+                sound = AudioManager.FX_KEYPRESS_STANDARD;
+                break;
+            }
+            mAudioManager.playSoundEffect(sound, mSettingsValues.mFxVolume);
+        }
+    }
+
+    // TODO: make this private when LatinIME does not call it any more
+    public void vibrate(final View viewToPerformHapticFeedbackOn) {
+        if (!mSettingsValues.mVibrateOn) {
+            return;
+        }
+        if (mSettingsValues.mKeypressVibrationDuration < 0) {
+            // Go ahead with the system default
+            if (viewToPerformHapticFeedbackOn != null) {
+                viewToPerformHapticFeedbackOn.performHapticFeedback(
+                        HapticFeedbackConstants.KEYBOARD_TAP,
+                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+            }
+        } else if (mVibratorUtils != null) {
+            mVibratorUtils.vibrate(mSettingsValues.mKeypressVibrationDuration);
+        }
+    }
+
+    public void onRingerModeChanged() {
+        mSoundOn = reevaluateIfSoundIsOn();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 485ec51..38444a1 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -16,57 +16,41 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.ArrayList;
-import java.util.Map;
+import java.util.HashMap;
 
 public class AutoCorrection {
     private static final boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = AutoCorrection.class.getSimpleName();
-    private boolean mHasAutoCorrection;
-    private CharSequence mAutoCorrectionWord;
-    private double mNormalizedScore;
 
-    public void init() {
-        mHasAutoCorrection = false;
-        mAutoCorrectionWord = null;
-        mNormalizedScore = Integer.MIN_VALUE;
+    private AutoCorrection() {
+        // Purely static class: can't instantiate.
     }
 
-    public boolean hasAutoCorrection() {
-        return mHasAutoCorrection;
-    }
-
-    public CharSequence getAutoCorrectionWord() {
-        return mAutoCorrectionWord;
-    }
-
-    public double getNormalizedScore() {
-        return mNormalizedScore;
-    }
-
-    public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
-            WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
-            CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
+    public static CharSequence computeAutoCorrectionWord(
+            HashMap<String, Dictionary> dictionaries,
+            WordComposer wordComposer, ArrayList<SuggestedWordInfo> suggestions,
+            CharSequence consideredWord, double autoCorrectionThreshold,
             CharSequence whitelistedWord) {
         if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
-            mHasAutoCorrection = true;
-            mAutoCorrectionWord = whitelistedWord;
-        } else if (hasAutoCorrectionForTypedWord(
-                dictionaries, wordComposer, suggestions, typedWord, correctionMode)) {
-            mHasAutoCorrection = true;
-            mAutoCorrectionWord = typedWord;
-        } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
-                sortedScores, typedWord, autoCorrectionThreshold)) {
-            mHasAutoCorrection = true;
-            mAutoCorrectionWord = suggestions.get(0);
+            return whitelistedWord;
+        } else if (hasAutoCorrectionForConsideredWord(
+                dictionaries, wordComposer, suggestions, consideredWord)) {
+            return consideredWord;
+        } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions,
+                consideredWord, autoCorrectionThreshold)) {
+            return suggestions.get(0).mWord;
         }
+        return null;
     }
 
     public static boolean isValidWord(
-            Map<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
+            HashMap<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
         if (TextUtils.isEmpty(word)) {
             return false;
         }
@@ -74,6 +58,14 @@
         for (final String key : dictionaries.keySet()) {
             if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue;
             final Dictionary dictionary = dictionaries.get(key);
+            // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
+            // managing to get null in here. Presumably the language is changing to a language with
+            // no main dictionary and the monkey manages to type a whole word before the thread
+            // that reads the dictionary is started or something?
+            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+            // would be immutable once it's finished initializing, but concretely a null test is
+            // probably good enough for the time being.
+            if (null == dictionary) continue;
             if (dictionary.isValidWord(word)
                     || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
                 return true;
@@ -83,7 +75,7 @@
     }
 
     public static boolean allowsToBeAutoCorrected(
-            Map<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
+            HashMap<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
         final WhitelistDictionary whitelistDictionary =
                 (WhitelistDictionary)dictionaries.get(Suggest.DICT_KEY_WHITELIST);
         // If "word" is in the whitelist dictionary, it should not be auto corrected.
@@ -98,34 +90,32 @@
         return whiteListedWord != null;
     }
 
-    private boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
-            WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
-            int correctionMode) {
-        if (TextUtils.isEmpty(typedWord)) return false;
-        boolean allowsAutoCorrect = allowsToBeAutoCorrected(dictionaries, typedWord, false);
-        return wordComposer.size() > 1 && suggestions.size() > 0 && !allowsAutoCorrect
-                && (correctionMode == Suggest.CORRECTION_FULL
-                || correctionMode == Suggest.CORRECTION_FULL_BIGRAM);
+    private static boolean hasAutoCorrectionForConsideredWord(
+            HashMap<String, Dictionary> dictionaries, WordComposer wordComposer,
+            ArrayList<SuggestedWordInfo> suggestions, CharSequence consideredWord) {
+        if (TextUtils.isEmpty(consideredWord)) return false;
+        return wordComposer.size() > 1 && suggestions.size() > 0
+                && !allowsToBeAutoCorrected(dictionaries, consideredWord, false);
     }
 
-    private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
-            ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores,
-            CharSequence typedWord, double autoCorrectionThreshold) {
-        if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
-                || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
-                && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) {
-            final CharSequence autoCorrectionSuggestion = suggestions.get(0);
-            final int autoCorrectionSuggestionScore = sortedScores[0];
+    private static boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
+            ArrayList<SuggestedWordInfo> suggestions,
+            CharSequence consideredWord, double autoCorrectionThreshold) {
+        if (wordComposer.size() > 1 && suggestions.size() > 0) {
+            final SuggestedWordInfo autoCorrectionSuggestion = suggestions.get(0);
+            //final int autoCorrectionSuggestionScore = sortedScores[0];
+            final int autoCorrectionSuggestionScore = autoCorrectionSuggestion.mScore;
             // TODO: when the normalized score of the first suggestion is nearly equals to
             //       the normalized score of the second suggestion, behave less aggressive.
-            mNormalizedScore = Utils.calcNormalizedScore(
-                    typedWord,autoCorrectionSuggestion, autoCorrectionSuggestionScore);
+            final double normalizedScore = BinaryDictionary.calcNormalizedScore(
+                    consideredWord.toString(), autoCorrectionSuggestion.mWord.toString(),
+                    autoCorrectionSuggestionScore);
             if (DBG) {
-                Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionSuggestion + ","
-                        + autoCorrectionSuggestionScore + ", " + mNormalizedScore
+                Log.d(TAG, "Normalized " + consideredWord + "," + autoCorrectionSuggestion + ","
+                        + autoCorrectionSuggestionScore + ", " + normalizedScore
                         + "(" + autoCorrectionThreshold + ")");
             }
-            if (mNormalizedScore >= autoCorrectionThreshold) {
+            if (normalizedScore >= autoCorrectionThreshold) {
                 if (DBG) {
                     Log.d(TAG, "Auto corrected by S-threshold.");
                 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b9fd574..c43683f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -40,14 +40,13 @@
     public static final int MAX_WORDS = 18;
 
     private static final String TAG = "BinaryDictionary";
-    private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
     private static final int MAX_BIGRAMS = 60;
 
     private static final int TYPED_LETTER_MULTIPLIER = 2;
 
     private int mDicTypeId;
-    private int mNativeDict;
-    private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
+    private long mNativeDict;
+    private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
     private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
     private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
     private final int[] mScores = new int[MAX_WORDS];
@@ -55,6 +54,8 @@
 
     public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
             new Flag(R.bool.config_require_umlaut_processing, 0x1);
+    public static final Flag FLAG_REQUIRES_FRENCH_LIGATURES_PROCESSING =
+            new Flag(R.bool.config_require_ligatures_processing, 0x4);
 
     // FULL_EDIT_DISTANCE is a flag that forces the dictionary to use full words
     // when computing edit distance, instead of the default behavior of stopping
@@ -77,6 +78,7 @@
         // actual value will be read from the configuration/extra value at run time for
         // the configuration at dictionary creation time.
         FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+        FLAG_REQUIRES_FRENCH_LIGATURES_PROCESSING,
     };
 
     private int mFlags = 0;
@@ -104,25 +106,27 @@
     }
 
     static {
-        Utils.loadNativeLibrary();
+        JniUtils.loadNativeLibrary();
     }
 
-    private native int openNative(String sourceDir, long dictOffset, long dictSize,
-            int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
-            int maxWords, int maxAlternatives);
-    private native void closeNative(int dict);
-    private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
-    private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
+    private native long openNative(String sourceDir, long dictOffset, long dictSize,
+            int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords);
+    private native void closeNative(long dict);
+    private native boolean isValidWordNative(long dict, char[] word, int wordLength);
+    private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
             int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
             int[] scores);
-    private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
+    private native int getBigramsNative(long dict, char[] prevWord, int prevWordLength,
             int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
-            int maxWordLength, int maxBigrams, int maxAlternatives);
+            int maxWordLength, int maxBigrams);
+    private static native double calcNormalizedScoreNative(
+            char[] before, int beforeLength, char[] after, int afterLength, int score);
+    private static native int editDistanceNative(
+            char[] before, int beforeLength, char[] after, int afterLength);
 
     private final void loadDictionary(String path, long startOffset, long length) {
         mNativeDict = openNative(path, startOffset, length,
-                    TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
-                    MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
+                TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS);
     }
 
     @Override
@@ -135,22 +139,19 @@
         Arrays.fill(mBigramScores, 0);
 
         int codesSize = codes.size();
-        if (codesSize <= 0) {
-            // Do not return bigrams from BinaryDictionary when nothing was typed.
-            // Only use user-history bigrams (or whatever other bigram dictionaries decide).
-            return;
-        }
         Arrays.fill(mInputCodes, -1);
-        int[] alternatives = codes.getCodesAt(0);
-        System.arraycopy(alternatives, 0, mInputCodes, 0,
-                Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+        if (codesSize > 0) {
+            mInputCodes[0] = codes.getCodeAt(0);
+        }
 
         int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
-                mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
-                MAX_PROXIMITY_CHARS_SIZE);
+                mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS);
+        if (count > MAX_BIGRAMS) {
+            count = MAX_BIGRAMS;
+        }
 
         for (int j = 0; j < count; ++j) {
-            if (mBigramScores[j] < 1) break;
+            if (codesSize > 0 && mBigramScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
             while (len <  MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
@@ -158,7 +159,7 @@
             }
             if (len > 0) {
                 callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
-                        mDicTypeId, DataType.BIGRAM);
+                        mDicTypeId, Dictionary.BIGRAM);
             }
         }
     }
@@ -178,7 +179,7 @@
             }
             if (len > 0) {
                 callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
-                        DataType.UNIGRAM);
+                        Dictionary.UNIGRAM);
             }
         }
     }
@@ -198,9 +199,7 @@
 
         Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
         for (int i = 0; i < codesSize; i++) {
-            int[] alternatives = codes.getCodesAt(i);
-            System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE,
-                    Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+            mInputCodes[i] = codes.getCodeAt(i);
         }
         Arrays.fill(outputChars, (char) 0);
         Arrays.fill(scores, 0);
@@ -211,6 +210,16 @@
                 mFlags, outputChars, scores);
     }
 
+    public static double calcNormalizedScore(String before, String after, int score) {
+        return calcNormalizedScoreNative(before.toCharArray(), before.length(),
+                after.toCharArray(), after.length(), score);
+    }
+
+    public static int editDistance(String before, String after) {
+        return editDistanceNative(
+                before.toCharArray(), before.length(), after.toCharArray(), after.length());
+    }
+
     @Override
     public boolean isValidWord(CharSequence word) {
         if (word == null) return false;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 9ffc7d0..311d3dc 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -196,8 +196,8 @@
             } finally {
                 // Ignore exceptions while closing files.
                 try {
-                    // afd.close() will close inputStream, we should not call inputStream.close().
-                    if (null != afd) afd.close();
+                    // inputStream.close() will close afd, we should not call afd.close().
+                    if (null != inputStream) inputStream.close();
                 } catch (Exception e) {
                     Log.e(TAG, "Exception while closing a cross-process file descriptor : " + e);
                 }
@@ -252,7 +252,7 @@
      * also apply.
      *
      * @param input the stream to be copied.
-     * @param outputFile an outputstream to copy the data to.
+     * @param output an output stream to copy the data to.
      */
     private static void checkMagicAndCopyFileTo(final BufferedInputStream input,
             final FileOutputStream output) throws FileNotFoundException, IOException {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index b333e48..e4d8396 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -23,9 +23,10 @@
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+
 import java.io.File;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Locale;
 
 /**
@@ -75,7 +76,8 @@
         // This assumes '%' is fully available as a non-separator, normal
         // character in a file name. This is probably true for all file systems.
         final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < name.length(); ++i) {
+        final int nameLength = name.length();
+        for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) {
             final int codePoint = name.codePointAt(i);
             if (isFileNameCharacter(codePoint)) {
                 sb.appendCodePoint(codePoint);
@@ -92,7 +94,8 @@
      */
     private static String getWordListIdFromFileName(final String fname) {
         final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < fname.length(); ++i) {
+        final int fnameLength = fname.length();
+        for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
             final int codePoint = fname.codePointAt(i);
             if ('%' != codePoint) {
                 sb.appendCodePoint(codePoint);
@@ -153,11 +156,13 @@
      */
     private static AssetFileAddress loadFallbackResource(final Context context,
             final int fallbackResId, final Locale locale) {
-        final Resources res = context.getResources();
-        final Locale savedLocale = LocaleUtils.setSystemLocale(res, locale);
-        final AssetFileDescriptor afd = res.openRawResourceFd(fallbackResId);
-        LocaleUtils.setSystemLocale(res, savedLocale);
-
+        final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
+            @Override
+            protected AssetFileDescriptor job(Resources res) {
+                return res.openRawResourceFd(fallbackResId);
+            }
+        };
+        final AssetFileDescriptor afd = job.runInLocale(context.getResources(), locale);
         if (afd == null) {
             Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
                     + fallbackResId);
@@ -262,9 +267,9 @@
      * - Gets a file name from the fallback resource passed as an argument.
      * If that fails:
      * - Returns null.
-     * @return The address of a valid file, or null.
+     * @return The list of addresses of valid dictionary files, or null.
      */
-    public static List<AssetFileAddress> getDictionaryFiles(final Locale locale,
+    public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale,
             final Context context, final int fallbackResId) {
 
         // cacheWordListsFromContentProvider returns the list of files it copied to local
diff --git a/java/src/com/android/inputmethod/latin/ComposingStateManager.java b/java/src/com/android/inputmethod/latin/ComposingStateManager.java
deleted file mode 100644
index 8811f20..0000000
--- a/java/src/com/android/inputmethod/latin/ComposingStateManager.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.util.Log;
-
-public class ComposingStateManager {
-    private static final String TAG = ComposingStateManager.class.getSimpleName();
-    private static final ComposingStateManager sInstance = new ComposingStateManager();
-    private boolean mAutoCorrectionIndicatorOn;
-    private boolean mIsComposing;
-
-    public static ComposingStateManager getInstance() {
-        return sInstance;
-    }
-
-    private ComposingStateManager() {
-        mAutoCorrectionIndicatorOn = false;
-        mIsComposing = false;
-    }
-
-    public synchronized void onStartComposingText() {
-        if (!mIsComposing) {
-            if (LatinImeLogger.sDBG) {
-                Log.i(TAG, "Start composing text.");
-            }
-            mAutoCorrectionIndicatorOn = false;
-            mIsComposing = true;
-        }
-    }
-
-    public synchronized void onFinishComposingText() {
-        if (mIsComposing) {
-            if (LatinImeLogger.sDBG) {
-                Log.i(TAG, "Finish composing text.");
-            }
-            mAutoCorrectionIndicatorOn = false;
-            mIsComposing = false;
-        }
-    }
-
-    public synchronized boolean isAutoCorrectionIndicatorOn() {
-        return mAutoCorrectionIndicatorOn;
-    }
-
-    public synchronized void setAutoCorrectionIndicatorOn(boolean on) {
-        // Auto-correction indicator should be specified only when the current state is "composing".
-        if (!mIsComposing) return;
-        if (LatinImeLogger.sDBG) {
-            Log.i(TAG, "Set auto correction Indicator: " + on);
-        }
-        mAutoCorrectionIndicatorOn = on;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2..23d63b4 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -25,11 +25,14 @@
 import android.preference.PreferenceActivity;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
 public class DebugSettings extends PreferenceActivity
         implements SharedPreferences.OnSharedPreferenceChangeListener {
 
-    private static final String TAG = "DebugSettings";
+    private static final String TAG = DebugSettings.class.getSimpleName();
     private static final String DEBUG_MODE_KEY = "debug_mode";
+    public static final String FORCE_NON_DISTINCT_MULTITOUCH_KEY = "force_non_distinct_multitouch";
 
     private boolean mServiceNeedsRestart = false;
     private CheckBoxPreference mDebugMode;
@@ -60,6 +63,9 @@
                 updateDebugMode();
                 mServiceNeedsRestart = true;
             }
+        } else if (key.equals(FORCE_NON_DISTINCT_MULTITOUCH_KEY)
+                || key.equals(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT)) {
+            mServiceNeedsRestart = true;
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index c35b428..9d26a23 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -33,13 +33,12 @@
      */
     protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
 
-    public static enum DataType {
-        UNIGRAM, BIGRAM
-    }
+    public static final int UNIGRAM = 0;
+    public static final int BIGRAM = 1;
 
     /**
      * Interface to be implemented by classes requesting words to be fetched from the dictionary.
-     * @see #getWords(WordComposer, WordCallback)
+     * @see #getWords(WordComposer, WordCallback, ProximityInfo)
      */
     public interface WordCallback {
         /**
@@ -51,11 +50,11 @@
          * @param score the score of occurrence. This is normalized between 1 and 255, but
          * can exceed those limits
          * @param dicTypeId of the dictionary where word was from
-         * @param dataType tells type of this data
+         * @param dataType tells type of this data, either UNIGRAM or BIGRAM
          * @return true if the word was added, false if no more words are required
          */
         boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
-                DataType dataType);
+                int dataType);
     }
 
     /**
@@ -64,7 +63,7 @@
      * @param composer the key sequence to match
      * @param callback the callback object to send matched words to as possible candidates
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
-     * @see WordCallback#addWord(char[], int, int, int, int, DataType)
+     * @see WordCallback#addWord(char[], int, int, int, int, int)
      */
     abstract public void getWords(final WordComposer composer, final WordCallback callback,
             final ProximityInfo proximityInfo);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 7391530..5de770a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -18,17 +18,18 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 
+import android.util.Log;
+
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Class for a collection of dictionaries that behave like one dictionary.
  */
 public class DictionaryCollection extends Dictionary {
-
-    protected final List<Dictionary> mDictionaries;
+    private final String TAG = DictionaryCollection.class.getSimpleName();
+    protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
 
     public DictionaryCollection() {
         mDictionaries = new CopyOnWriteArrayList<Dictionary>();
@@ -75,7 +76,21 @@
             dict.close();
     }
 
-    public void addDictionary(Dictionary newDict) {
-        if (null != newDict) mDictionaries.add(newDict);
+    // Warning: this is not thread-safe. Take necessary precaution when calling.
+    public void addDictionary(final Dictionary newDict) {
+        if (null == newDict) return;
+        if (mDictionaries.contains(newDict)) {
+            Log.w(TAG, "This collection already contains this dictionary: " + newDict);
+        }
+        mDictionaries.add(newDict);
+    }
+
+    // Warning: this is not thread-safe. Take necessary precaution when calling.
+    public void removeDictionary(final Dictionary dict) {
+        if (mDictionaries.contains(dict)) {
+            mDictionaries.remove(dict);
+        } else {
+            Log.w(TAG, "This collection does not contain this dictionary: " + dict);
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 1607f86..7be374d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -21,16 +21,17 @@
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+
 import java.io.File;
+import java.util.ArrayList;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Locale;
 
 /**
  * Factory for dictionary instances.
  */
 public class DictionaryFactory {
-
     private static String TAG = DictionaryFactory.class.getSimpleName();
 
     /**
@@ -52,8 +53,8 @@
             return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, locale));
         }
 
-        final List<Dictionary> dictList = new LinkedList<Dictionary>();
-        final List<AssetFileAddress> assetFileList =
+        final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
+        final ArrayList<AssetFileAddress> assetFileList =
                 BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
         if (null != assetFileList) {
             for (final AssetFileAddress f : assetFileList) {
@@ -98,14 +99,13 @@
             final int resId, final Locale locale) {
         AssetFileDescriptor afd = null;
         try {
-            final Resources res = context.getResources();
-            if (null != locale) {
-                final Locale savedLocale = LocaleUtils.setSystemLocale(res, locale);
-                afd = res.openRawResourceFd(resId);
-                LocaleUtils.setSystemLocale(res, savedLocale);
-            } else {
-                afd = res.openRawResourceFd(resId);
-            }
+            final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
+                @Override
+                protected AssetFileDescriptor job(Resources res) {
+                    return res.openRawResourceFd(resId);
+                }
+            };
+            afd = job.runInLocale(context.getResources(), locale);
             if (afd == null) {
                 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
                 return null;
@@ -161,39 +161,41 @@
      * @return whether a (non-placeholder) dictionary is available or not.
      */
     public static boolean isDictionaryAvailable(Context context, Locale locale) {
-        final Resources res = context.getResources();
-        final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
-
-        final int resourceId = Utils.getMainDictionaryResourceId(res);
-        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
-        final boolean hasDictionary = isFullDictionary(afd);
-        try {
-            if (null != afd) afd.close();
-        } catch (java.io.IOException e) {
-            /* Um, what can we do here exactly? */
-        }
-
-        LocaleUtils.setSystemLocale(res, saveLocale);
-        return hasDictionary;
+        final RunInLocale<Boolean> job = new RunInLocale<Boolean>() {
+            @Override
+            protected Boolean job(Resources res) {
+                final int resourceId = getMainDictionaryResourceId(res);
+                final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+                final boolean hasDictionary = isFullDictionary(afd);
+                try {
+                    if (null != afd) afd.close();
+                } catch (java.io.IOException e) {
+                    /* Um, what can we do here exactly? */
+                }
+                return hasDictionary;
+            }
+        };
+        return job.runInLocale(context.getResources(), locale);
     }
 
     // TODO: Do not use the size of the dictionary as an unique dictionary ID.
     public static Long getDictionaryId(final Context context, final Locale locale) {
-        final Resources res = context.getResources();
-        final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
-
-        final int resourceId = Utils.getMainDictionaryResourceId(res);
-        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
-        final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
-                ? afd.getLength()
-                : null;
-        try {
-            if (null != afd) afd.close();
-        } catch (java.io.IOException e) {
-        }
-
-        LocaleUtils.setSystemLocale(res, saveLocale);
-        return size;
+        final RunInLocale<Long> job = new RunInLocale<Long>() {
+            @Override
+            protected Long job(Resources res) {
+                final int resourceId = getMainDictionaryResourceId(res);
+                final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+                final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
+                        ? afd.getLength()
+                        : null;
+                try {
+                    if (null != afd) afd.close();
+                } catch (java.io.IOException e) {
+                }
+                return size;
+            }
+        };
+        return job.runInLocale(context.getResources(), locale);
     }
 
     // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
@@ -209,4 +211,14 @@
     protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
         return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
     }
+
+    /**
+     * Returns a main dictionary resource id
+     * @return main dictionary resource id
+     */
+    public static int getMainDictionaryResourceId(Resources res) {
+        final String MAIN_DIC_NAME = "main";
+        String packageName = LatinIME.class.getPackage().getName();
+        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 634dbbd..b3f613b 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -1,12 +1,12 @@
 /*
  * Copyright (C) 2009 The Android Open Source Project
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * 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
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.compat.InputConnectionCompatUtils;
-
 import android.text.TextUtils;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
@@ -87,23 +85,6 @@
     }
 
     /**
-     * Removes the word surrounding the cursor. Parameters are identical to
-     * getWordAtCursor.
-     */
-    public static void deleteWordAtCursor(InputConnection connection, String separators) {
-        // getWordRangeAtCursor returns null if the connection is null
-        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.mCharsBefore;
-        connection.setSelection(newCursor, newCursor);
-        connection.deleteSurroundingText(0, range.mCharsBefore + range.mCharsAfter);
-    }
-
-    /**
      * Represents a range of text, relative to the current cursor position.
      */
     public static class Range {
@@ -263,7 +244,7 @@
         if (selStart == selEnd) {
             // There is just a cursor, so get the word at the cursor
             // getWordRangeAtCursor returns null if the connection is null
-            EditingUtils.Range range = getWordRangeAtCursor(ic, wordSeparators);
+            final EditingUtils.Range range = getWordRangeAtCursor(ic, wordSeparators);
             if (range != null && !TextUtils.isEmpty(range.mWord)) {
                 return new SelectedWord(selStart - range.mCharsBefore, selEnd + range.mCharsAfter,
                         range.mWord);
@@ -271,20 +252,19 @@
         } else {
             if (null == ic) return null;
             // Is the previous character empty or a word separator? If not, return null.
-            CharSequence charsBefore = ic.getTextBeforeCursor(1, 0);
+            final CharSequence charsBefore = ic.getTextBeforeCursor(1, 0);
             if (!isWordBoundary(charsBefore, wordSeparators)) {
                 return null;
             }
 
             // Is the next character empty or a word separator? If not, return null.
-            CharSequence charsAfter = ic.getTextAfterCursor(1, 0);
+            final CharSequence charsAfter = ic.getTextAfterCursor(1, 0);
             if (!isWordBoundary(charsAfter, wordSeparators)) {
                 return null;
             }
 
             // Extract the selection alone
-            CharSequence touching = InputConnectionCompatUtils.getSelectedText(
-                    ic, selStart, selEnd);
+            final CharSequence touching = ic.getSelectedText(0);
             if (TextUtils.isEmpty(touching)) return null;
             // Is any part of the selection a separator? If so, return null.
             final int length = touching.length();
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index cad69bb..46d11fa 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.ProximityInfo;
 
@@ -28,17 +29,12 @@
  * 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;
 
     // Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8.
     protected static final int BIGRAM_MAX_FREQUENCY = 255;
 
     private Context mContext;
-    private char[] mWordBuilder = new char[MAX_WORD_LENGTH];
+    private char[] mWordBuilder = new char[BinaryDictionary.MAX_WORD_LENGTH];
     private int mDicTypeId;
     private int mMaxDepth;
     private int mInputLength;
@@ -51,6 +47,7 @@
     private Object mUpdatingLock = new Object();
 
     private static class Node {
+        Node() {}
         char mCode;
         int mFrequency;
         boolean mTerminal;
@@ -112,7 +109,7 @@
     public ExpandableDictionary(Context context, int dicTypeId) {
         mContext = context;
         clearDictionary();
-        mCodes = new int[MAX_WORD_LENGTH][];
+        mCodes = new int[BinaryDictionary.MAX_WORD_LENGTH][];
         mDicTypeId = dicTypeId;
     }
 
@@ -150,10 +147,13 @@
     }
 
     public int getMaxWordLength() {
-        return MAX_WORD_LENGTH;
+        return BinaryDictionary.MAX_WORD_LENGTH;
     }
 
     public void addWord(String word, int frequency) {
+        if (word.length() >= BinaryDictionary.MAX_WORD_LENGTH) {
+            return;
+        }
         addWordRec(mRoots, word, 0, frequency, null);
     }
 
@@ -200,6 +200,9 @@
             // Currently updating contacts, don't return any results.
             if (mUpdatingDictionary) return;
         }
+        if (codes.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
+            return;
+        }
         getWordsInner(codes, callback, proximityInfo);
     }
 
@@ -207,9 +210,19 @@
             @SuppressWarnings("unused") final ProximityInfo proximityInfo) {
         mInputLength = codes.size();
         if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
+        final int[] xCoordinates = codes.getXCoordinates();
+        final int[] yCoordinates = codes.getYCoordinates();
         // Cache the codes so that we don't have to lookup an array list
         for (int i = 0; i < mInputLength; i++) {
-            mCodes[i] = codes.getCodesAt(i);
+            // TODO: Calculate proximity info here.
+            if (mCodes[i] == null || mCodes[i].length < 1) {
+                mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
+            }
+            final int x = xCoordinates != null && i < xCoordinates.length ?
+                    xCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+            final int y = xCoordinates != null && i < yCoordinates.length ?
+                    yCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+            proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
         }
         mMaxDepth = mInputLength * 3;
         getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, callback);
@@ -300,7 +313,7 @@
                         finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
                     }
                     if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
-                            DataType.UNIGRAM)) {
+                            Dictionary.UNIGRAM)) {
                         return;
                     }
                 }
@@ -318,11 +331,11 @@
                 }
             } else {
                 // Don't use alternatives if we're looking for missing characters
-                final int alternativesSize = skipPos >= 0? 1 : currentChars.length;
+                final int alternativesSize = skipPos >= 0 ? 1 : currentChars.length;
                 for (int j = 0; j < alternativesSize; j++) {
                     final int addedAttenuation = (j > 0 ? 1 : 2);
                     final int currentChar = currentChars[j];
-                    if (currentChar == -1) {
+                    if (currentChar == KeyDetector.NOT_A_CODE) {
                         break;
                     }
                     if (currentChar == lowerC || currentChar == c) {
@@ -341,7 +354,7 @@
                                                 snr * addedAttenuation, mInputLength);
                                     }
                                     callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
-                                            DataType.UNIGRAM);
+                                            Dictionary.UNIGRAM);
                                 }
                             }
                             if (children != null) {
@@ -483,7 +496,7 @@
     }
 
     // Local to reverseLookUp, but do not allocate each time.
-    private final char[] mLookedUpString = new char[MAX_WORD_LENGTH];
+    private final char[] mLookedUpString = new char[BinaryDictionary.MAX_WORD_LENGTH];
 
     /**
      * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
@@ -497,15 +510,15 @@
         for (NextWord nextWord : terminalNodes) {
             node = nextWord.mWord;
             freq = nextWord.getFrequency();
-            int index = MAX_WORD_LENGTH;
+            int index = BinaryDictionary.MAX_WORD_LENGTH;
             do {
                 --index;
                 mLookedUpString[index] = node.mCode;
                 node = node.mParent;
             } while (node != null);
 
-            callback.addWord(mLookedUpString, index, MAX_WORD_LENGTH - index, freq, mDicTypeId,
-                    DataType.BIGRAM);
+            callback.addWord(mLookedUpString, index, BinaryDictionary.MAX_WORD_LENGTH - index,
+                    freq, mDicTypeId, Dictionary.BIGRAM);
         }
     }
 
@@ -547,6 +560,7 @@
     }
 
     private class LoadDictionaryTask extends Thread {
+        LoadDictionaryTask() {}
         @Override
         public void run() {
             loadDictionaryAsync();
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
new file mode 100644
index 0000000..a6ce040
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.InputType;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+/**
+ * Class to hold attributes of the input field.
+ */
+public class InputAttributes {
+    private final String TAG = InputAttributes.class.getSimpleName();
+
+    final public boolean mInputTypeNoAutoCorrect;
+    final public boolean mIsSettingsSuggestionStripOn;
+    final public boolean mApplicationSpecifiedCompletionOn;
+
+    public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
+        final int inputType = null != editorInfo ? editorInfo.inputType : 0;
+        final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+        if (inputClass != InputType.TYPE_CLASS_TEXT) {
+            // If we are not looking at a TYPE_CLASS_TEXT field, the following strange
+            // cases may arise, so we do a couple sanity checks for them. If it's a
+            // TYPE_CLASS_TEXT field, these special cases cannot happen, by construction
+            // of the flags.
+            if (null == editorInfo) {
+                Log.w(TAG, "No editor info for this field. Bug?");
+            } else if (InputType.TYPE_NULL == inputType) {
+                // TODO: We should honor TYPE_NULL specification.
+                Log.i(TAG, "InputType.TYPE_NULL is specified");
+            } else if (inputClass == 0) {
+                // TODO: is this check still necessary?
+                Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
+                        + " imeOptions=0x%08x",
+                        inputType, editorInfo.imeOptions));
+            }
+            mIsSettingsSuggestionStripOn = false;
+            mInputTypeNoAutoCorrect = false;
+            mApplicationSpecifiedCompletionOn = false;
+        } else {
+            final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+            final boolean flagNoSuggestions =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+            final boolean flagMultiLine =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+            final boolean flagAutoCorrect =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
+            final boolean flagAutoComplete =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+
+            // Make sure that passwords are not displayed in {@link SuggestionsView}.
+            if (InputTypeUtils.isPasswordInputType(inputType)
+                    || InputTypeUtils.isVisiblePasswordInputType(inputType)
+                    || InputTypeUtils.isEmailVariation(variation)
+                    || InputType.TYPE_TEXT_VARIATION_URI == variation
+                    || InputType.TYPE_TEXT_VARIATION_FILTER == variation
+                    || flagNoSuggestions
+                    || flagAutoComplete) {
+                mIsSettingsSuggestionStripOn = false;
+            } else {
+                mIsSettingsSuggestionStripOn = true;
+            }
+
+            // If it's a browser edit field and auto correct is not ON explicitly, then
+            // disable auto correction, but keep suggestions on.
+            // If NO_SUGGESTIONS is set, don't do prediction.
+            // If it's not multiline and the autoCorrect flag is not set, then don't correct
+            if ((variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
+                    && !flagAutoCorrect)
+                    || flagNoSuggestions
+                    || (!flagAutoCorrect && !flagMultiLine)) {
+                mInputTypeNoAutoCorrect = true;
+            } else {
+                mInputTypeNoAutoCorrect = false;
+            }
+
+            mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private void dumpFlags(final int inputType) {
+        Log.i(TAG, "Input class:");
+        final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+        if (inputClass == InputType.TYPE_CLASS_TEXT)
+            Log.i(TAG, "  TYPE_CLASS_TEXT");
+        if (inputClass == InputType.TYPE_CLASS_PHONE)
+            Log.i(TAG, "  TYPE_CLASS_PHONE");
+        if (inputClass == InputType.TYPE_CLASS_NUMBER)
+            Log.i(TAG, "  TYPE_CLASS_NUMBER");
+        if (inputClass == InputType.TYPE_CLASS_DATETIME)
+            Log.i(TAG, "  TYPE_CLASS_DATETIME");
+        Log.i(TAG, "Variation:");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_EMAIL_ADDRESS");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_EMAIL_SUBJECT");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_FILTER))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_FILTER");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_LONG_MESSAGE");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_NORMAL))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_NORMAL");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_PASSWORD");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PERSON_NAME))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_PERSON_NAME");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PHONETIC))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_PHONETIC");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_POSTAL_ADDRESS");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_SHORT_MESSAGE");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_URI))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_URI");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_VISIBLE_PASSWORD");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_WEB_EDIT_TEXT");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
+        if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
+            Log.i(TAG, "  TYPE_TEXT_VARIATION_WEB_PASSWORD");
+        Log.i(TAG, "Flags:");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_NO_SUGGESTIONS");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_MULTI_LINE");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_IME_MULTI_LINE");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_CAP_WORDS");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_CAP_SENTENCES");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_CAP_CHARACTERS");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_AUTO_CORRECT");
+        if (0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE))
+            Log.i(TAG, "  TYPE_TEXT_FLAG_AUTO_COMPLETE");
+    }
+
+    // Pretty print
+    @Override
+    public String toString() {
+        return "\n mInputTypeNoAutoCorrect = " + mInputTypeNoAutoCorrect
+                + "\n mIsSettingsSuggestionStripOn = " + mIsSettingsSuggestionStripOn
+                + "\n mApplicationSpecifiedCompletionOn = " + mApplicationSpecifiedCompletionOn;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
new file mode 100644
index 0000000..40c3b76
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.InputType;
+
+public class InputTypeUtils implements InputType {
+    private static final int WEB_TEXT_PASSWORD_INPUT_TYPE =
+            TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD;
+    private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE =
+            TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+    private static final int NUMBER_PASSWORD_INPUT_TYPE =
+            TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD;
+    private static final int TEXT_PASSWORD_INPUT_TYPE =
+            TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
+    private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE =
+            TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+
+    private InputTypeUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    private static boolean isWebEditTextInputType(int inputType) {
+        return inputType == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+    }
+
+    private static boolean isWebPasswordInputType(int inputType) {
+        return WEB_TEXT_PASSWORD_INPUT_TYPE != 0
+                && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isWebEmailAddressInputType(int inputType) {
+        return WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE != 0
+                && inputType == WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE;
+    }
+
+    private static boolean isNumberPasswordInputType(int inputType) {
+        return NUMBER_PASSWORD_INPUT_TYPE != 0
+                && inputType == NUMBER_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isTextPasswordInputType(int inputType) {
+        return inputType == TEXT_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isWebEmailAddressVariation(int variation) {
+        return variation == TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+    }
+
+    public static boolean isEmailVariation(int variation) {
+        return variation == TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                || isWebEmailAddressVariation(variation);
+    }
+
+    public static boolean isWebInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION);
+        return isWebEditTextInputType(maskedInputType) || isWebPasswordInputType(maskedInputType)
+                || isWebEmailAddressInputType(maskedInputType);
+    }
+
+    // Please refer to TextView.isPasswordInputType
+    public static boolean isPasswordInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION);
+        return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType)
+                || isNumberPasswordInputType(maskedInputType);
+    }
+
+    // Please refer to TextView.isVisiblePasswordInputType
+    public static boolean isVisiblePasswordInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION);
+        return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
new file mode 100644
index 0000000..4808b86
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.Log;
+
+import com.android.inputmethod.latin.define.JniLibName;
+
+public class JniUtils {
+    private static final String TAG = JniUtils.class.getSimpleName();
+
+    private JniUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void loadNativeLibrary() {
+        try {
+            System.loadLibrary(JniLibName.JNI_LIB_NAME);
+        } catch (UnsatisfiedLinkError ule) {
+            Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME);
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException(
+                        "Could not load native library " + JniLibName.JNI_LIB_NAME);
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
new file mode 100644
index 0000000..af0ef4b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.TextUtils;
+
+/**
+ * This class encapsulates data about a word previously composed, but that has been
+ * committed already. This is used for resuming suggestion, and cancel auto-correction.
+ */
+public class LastComposedWord {
+    // COMMIT_TYPE_USER_TYPED_WORD is used when the word committed is the exact typed word, with
+    // no hinting from the IME. It happens when some external event happens (rotating the device,
+    // for example) or when auto-correction is off by settings or editor attributes.
+    public static final int COMMIT_TYPE_USER_TYPED_WORD = 0;
+    // COMMIT_TYPE_MANUAL_PICK is used when the user pressed a field in the suggestion strip.
+    public static final int COMMIT_TYPE_MANUAL_PICK = 1;
+    // COMMIT_TYPE_DECIDED_WORD is used when the IME commits the word it decided was best
+    // for the current user input. It may be different from what the user typed (true auto-correct)
+    // or it may be exactly what the user typed if it's in the dictionary or the IME does not have
+    // enough confidence in any suggestion to auto-correct (auto-correct to typed word).
+    public static final int COMMIT_TYPE_DECIDED_WORD = 2;
+    // COMMIT_TYPE_CANCEL_AUTO_CORRECT is used upon committing back the old word upon cancelling
+    // an auto-correction.
+    public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3;
+
+    public static final int NOT_A_SEPARATOR = -1;
+
+    public final int[] mPrimaryKeyCodes;
+    public final int[] mXCoordinates;
+    public final int[] mYCoordinates;
+    public final String mTypedWord;
+    public final String mCommittedWord;
+    public final int mSeparatorCode;
+
+    private boolean mActive;
+
+    public static final LastComposedWord NOT_A_COMPOSED_WORD =
+            new LastComposedWord(null, null, null, "", "", NOT_A_SEPARATOR);
+
+    // Warning: this is using the passed objects as is and fully expects them to be
+    // immutable. Do not fiddle with their contents after you passed them to this constructor.
+    public LastComposedWord(final int[] primaryKeyCodes, final int[] xCoordinates,
+            final int[] yCoordinates, final String typedWord, final String committedWord,
+            final int separatorCode) {
+        mPrimaryKeyCodes = primaryKeyCodes;
+        mXCoordinates = xCoordinates;
+        mYCoordinates = yCoordinates;
+        mTypedWord = typedWord;
+        mCommittedWord = committedWord;
+        mSeparatorCode = separatorCode;
+        mActive = true;
+    }
+
+    public void deactivate() {
+        mActive = false;
+    }
+
+    public boolean canRevertCommit() {
+        return mActive && !TextUtils.isEmpty(mCommittedWord);
+    }
+
+    public boolean didCommitTypedWord() {
+        return TextUtils.equals(mTypedWord, mCommittedWord);
+    }
+
+    public static int getSeparatorLength(final int separatorCode) {
+        return NOT_A_SEPARATOR == separatorCode ? 0 : 1;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 249dc56..db57044 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -30,6 +30,7 @@
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.SystemClock;
 import android.preference.PreferenceActivity;
@@ -39,47 +40,45 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
-import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.view.Window;
+import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.compat.CompatUtils;
-import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.compat.InputConnectionCompatUtils;
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.compat.SuggestionSpanUtils;
-import com.android.inputmethod.compat.VibratorCompatWrapper;
-import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
-import com.android.inputmethod.deprecated.VoiceProxy;
-import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.LatinKeyboard;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.suggestions.SuggestionsView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Locale;
 
 /**
  * Input method implementation for Qwerty'ish keyboard.
  */
-public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener,
+public class LatinIME extends InputMethodService implements KeyboardActionListener,
         SuggestionsView.Listener {
     private static final String TAG = LatinIME.class.getSimpleName();
-    private static final boolean PERF_DEBUG = false;
     private static final boolean TRACE = false;
     private static boolean DEBUG;
 
@@ -109,7 +108,10 @@
     /**
      * The private IME option used to indicate that the given text field needs
      * ASCII code points input.
+     *
+     * @deprecated Use {@link EditorInfo#IME_FLAG_FORCE_ASCII}.
      */
+    @SuppressWarnings("dep-ann")
     public static final String IME_OPTION_FORCE_ASCII = "forceAscii";
 
     /**
@@ -119,12 +121,6 @@
     public static final String SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE = "AsciiCapable";
 
     /**
-     * The subtype extra value used to indicate that the subtype keyboard layout supports touch
-     * position correction.
-     */
-    public static final String SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION =
-            "SupportTouchPositionCorrection";
-    /**
      * The subtype extra value used to indicate that the subtype keyboard layout should be loaded
      * from the specified locale.
      */
@@ -145,6 +141,7 @@
      */
     private static final String SCHEME_PACKAGE = "package";
 
+    // TODO: migrate this to SettingsValues
     private int mSuggestionVisibility;
     private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
             = R.string.prefs_suggestion_visibility_show_value;
@@ -159,51 +156,55 @@
         SUGGESTION_VISIBILILTY_HIDE_VALUE
     };
 
-    private Settings.Values mSettingsValues;
+    private static final int SPACE_STATE_NONE = 0;
+    // Double space: the state where the user pressed space twice quickly, which LatinIME
+    // resolved as period-space. Undoing this converts the period to a space.
+    private static final int SPACE_STATE_DOUBLE = 1;
+    // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip
+    // have just been swapped. Undoing this swaps them back; the space is still considered weak.
+    private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
+    // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak
+    // spaces happen when the user presses space, accepting the current suggestion (whether
+    // it's an auto-correction or not).
+    private static final int SPACE_STATE_WEAK = 3;
+    // Phantom space: a not-yet-inserted space that should get inserted on the next input,
+    // character provided it's not a separator. If it's a separator, the phantom space is dropped.
+    // Phantom spaces happen when a user chooses a word from the suggestion strip.
+    private static final int SPACE_STATE_PHANTOM = 4;
+
+    // Current space state of the input method. This can be any of the above constants.
+    private int mSpaceState;
+
+    private SettingsValues mSettingsValues;
+    private InputAttributes mInputAttributes;
 
     private View mExtractArea;
     private View mKeyPreviewBackingView;
     private View mSuggestionsContainer;
     private SuggestionsView mSuggestionsView;
-    private Suggest mSuggest;
+    /* package for tests */ Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
 
     private InputMethodManagerCompatWrapper mImm;
     private Resources mResources;
     private SharedPreferences mPrefs;
-    private String mInputMethodId;
-    private KeyboardSwitcher mKeyboardSwitcher;
-    private SubtypeSwitcher mSubtypeSwitcher;
-    private VoiceProxy mVoiceProxy;
+    /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher;
+    private final SubtypeSwitcher mSubtypeSwitcher;
+    private boolean mShouldSwitchToLastSubtype = true;
 
     private UserDictionary mUserDictionary;
-    private UserBigramDictionary mUserBigramDictionary;
-    private UserUnigramDictionary mUserUnigramDictionary;
-    private boolean mIsUserDictionaryAvaliable;
+    private UserHistoryDictionary mUserHistoryDictionary;
+    private boolean mIsUserDictionaryAvailable;
 
-    // TODO: Create an inner class to group options and pseudo-options to improve readability.
-    // These variables are initialized according to the {@link EditorInfo#inputType}.
-    private boolean mInsertSpaceOnPickSuggestionManually;
-    private boolean mInputTypeNoAutoCorrect;
-    private boolean mIsSettingsSuggestionStripOn;
-    private boolean mApplicationSpecifiedCompletionOn;
-
-    private final StringBuilder mComposingStringBuilder = new StringBuilder();
+    private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     private WordComposer mWordComposer = new WordComposer();
-    private CharSequence mBestWord;
-    private boolean mHasUncommittedTypedChars;
-    // Magic space: a space that should disappear on space/apostrophe insertion, move after the
-    // punctuation on punctuation insertion, and become a real space on alpha char insertion.
-    private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
-    // This indicates whether the last keypress resulted in processing of double space replacement
-    // with period-space.
-    private boolean mJustReplacedDoubleSpace;
 
     private int mCorrectionMode;
-    private int mCommittedLength;
+
     // Keep track of the last selection range to decide if we need to show word alternatives
-    private int mLastSelectionStart;
-    private int mLastSelectionEnd;
+    private static final int NOT_A_CURSOR_POSITION = -1;
+    private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
+    private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
 
     // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
     // "expect" it, it means the user actually moved the cursor.
@@ -211,15 +212,7 @@
     private int mDeleteCount;
     private long mLastKeyTime;
 
-    private AudioManager mAudioManager;
-    private float mFxVolume = -1.0f; // default volume
-    private boolean mSilentModeOn; // System-wide current configuration
-
-    private VibratorCompatWrapper mVibrator;
-    private long mKeypressVibrationDuration = -1;
-
-    // TODO: Move this flag to VoiceProxy
-    private boolean mConfigurationChanging;
+    private AudioAndHapticFeedbackManager mFeedbackManager;
 
     // Member variables for remembering the current device orientation.
     private int mDisplayOrientation;
@@ -231,29 +224,22 @@
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
 
-    private final ComposingStateManager mComposingStateManager =
-            ComposingStateManager.getInstance();
+    private boolean mIsAutoCorrectionIndicatorOn;
+
+    private AlertDialog mOptionsDialog;
 
     public final UIHandler mHandler = new UIHandler(this);
 
     public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
-        private static final int MSG_UPDATE_SUGGESTIONS = 0;
         private static final int MSG_UPDATE_SHIFT_STATE = 1;
-        private static final int MSG_VOICE_RESULTS = 2;
-        private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 3;
-        private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 4;
-        private static final int MSG_SPACE_TYPED = 5;
-        private static final int MSG_KEY_TYPED = 6;
-        private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
-        private static final int MSG_PENDING_IMS_CALLBACK = 8;
+        private static final int MSG_SPACE_TYPED = 4;
+        private static final int MSG_SET_BIGRAM_PREDICTIONS = 5;
+        private static final int MSG_PENDING_IMS_CALLBACK = 6;
+        private static final int MSG_UPDATE_SUGGESTIONS = 7;
 
-        private int mDelayBeforeFadeoutLanguageOnSpacebar;
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
-        private int mDurationOfFadeoutLanguageOnSpacebar;
-        private float mFinalFadeoutFactorOfLanguageOnSpacebar;
         private long mDoubleSpacesTurnIntoPeriodTimeout;
-        private long mIgnoreSpecialKeyTimeout;
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -261,27 +247,18 @@
 
         public void onCreate() {
             final Resources res = getOuterInstance().getResources();
-            mDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
-                    R.integer.config_delay_before_fadeout_language_on_spacebar);
             mDelayUpdateSuggestions =
                     res.getInteger(R.integer.config_delay_update_suggestions);
             mDelayUpdateShiftState =
                     res.getInteger(R.integer.config_delay_update_shift_state);
-            mDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
-                    R.integer.config_duration_of_fadeout_language_on_spacebar);
-            mFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
-                    R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
             mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
                     R.integer.config_double_spaces_turn_into_period_timeout);
-            mIgnoreSpecialKeyTimeout = res.getInteger(
-                    R.integer.config_ignore_special_key_timeout);
         }
 
         @Override
         public void handleMessage(Message msg) {
             final LatinIME latinIme = getOuterInstance();
             final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
-            final LatinKeyboardView inputView = switcher.getKeyboardView();
             switch (msg.what) {
             case MSG_UPDATE_SUGGESTIONS:
                 latinIme.updateSuggestions();
@@ -292,25 +269,6 @@
             case MSG_SET_BIGRAM_PREDICTIONS:
                 latinIme.updateBigramPredictions();
                 break;
-            case MSG_VOICE_RESULTS:
-                latinIme.mVoiceProxy.handleVoiceResults(latinIme.preferCapitalization()
-                        || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
-                break;
-            case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null) {
-                    inputView.setSpacebarTextFadeFactor(
-                            (1.0f + mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
-                            (LatinKeyboard)msg.obj);
-                }
-                sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj),
-                        mDurationOfFadeoutLanguageOnSpacebar);
-                break;
-            case MSG_DISMISS_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null) {
-                    inputView.setSpacebarTextFadeFactor(mFinalFadeoutFactorOfLanguageOnSpacebar,
-                            (LatinKeyboard)msg.obj);
-                }
-                break;
             }
         }
 
@@ -327,7 +285,7 @@
             return hasMessages(MSG_UPDATE_SUGGESTIONS);
         }
 
-        public void postUpdateShiftKeyState() {
+        public void postUpdateShiftState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
         }
@@ -345,34 +303,6 @@
             removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
         }
 
-        public void updateVoiceResults() {
-            sendMessage(obtainMessage(MSG_VOICE_RESULTS));
-        }
-
-        public void startDisplayLanguageOnSpacebar(boolean localeChanged) {
-            final LatinIME latinIme = getOuterInstance();
-            removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR);
-            removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
-            final LatinKeyboardView inputView = latinIme.mKeyboardSwitcher.getKeyboardView();
-            if (inputView != null) {
-                final LatinKeyboard keyboard = latinIme.mKeyboardSwitcher.getLatinKeyboard();
-                // The language is always displayed when the delay is negative.
-                final boolean needsToDisplayLanguage = localeChanged
-                        || mDelayBeforeFadeoutLanguageOnSpacebar < 0;
-                // The language is never displayed when the delay is zero.
-                if (mDelayBeforeFadeoutLanguageOnSpacebar != 0) {
-                    inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f
-                            : mFinalFadeoutFactorOfLanguageOnSpacebar,
-                            keyboard);
-                }
-                // The fadeout animation will start when the delay is positive.
-                if (localeChanged && mDelayBeforeFadeoutLanguageOnSpacebar > 0) {
-                    sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard),
-                            mDelayBeforeFadeoutLanguageOnSpacebar);
-                }
-            }
-        }
-
         public void startDoubleSpacesTimer() {
             removeMessages(MSG_SPACE_TYPED);
             sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), mDoubleSpacesTurnIntoPeriodTimeout);
@@ -386,21 +316,13 @@
             return hasMessages(MSG_SPACE_TYPED);
         }
 
-        public void startKeyTypedTimer() {
-            removeMessages(MSG_KEY_TYPED);
-            sendMessageDelayed(obtainMessage(MSG_KEY_TYPED), mIgnoreSpecialKeyTimeout);
-        }
-
-        public boolean isIgnoringSpecialKey() {
-            return hasMessages(MSG_KEY_TYPED);
-        }
-
         // Working variables for the following methods.
         private boolean mIsOrientationChanging;
-        private boolean mPendingSuccesiveImsCallback;
+        private boolean mPendingSuccessiveImsCallback;
         private boolean mHasPendingStartInput;
         private boolean mHasPendingFinishInputView;
         private boolean mHasPendingFinishInput;
+        private EditorInfo mAppliedEditorInfo;
 
         public void startOrientationChanging() {
             removeMessages(MSG_PENDING_IMS_CALLBACK);
@@ -418,18 +340,18 @@
             mHasPendingStartInput = false;
         }
 
-        private void executePendingImsCallback(LatinIME latinIme, EditorInfo attribute,
+        private void executePendingImsCallback(LatinIME latinIme, EditorInfo editorInfo,
                 boolean restarting) {
             if (mHasPendingFinishInputView)
                 latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
             if (mHasPendingFinishInput)
                 latinIme.onFinishInputInternal();
             if (mHasPendingStartInput)
-                latinIme.onStartInputInternal(attribute, restarting);
+                latinIme.onStartInputInternal(editorInfo, restarting);
             resetPendingImsCallback();
         }
 
-        public void onStartInput(EditorInfo attribute, boolean restarting) {
+        public void onStartInput(EditorInfo editorInfo, boolean restarting) {
             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
                 // Typically this is the second onStartInput after orientation changed.
                 mHasPendingStartInput = true;
@@ -437,30 +359,32 @@
                 if (mIsOrientationChanging && restarting) {
                     // This is the first onStartInput after orientation changed.
                     mIsOrientationChanging = false;
-                    mPendingSuccesiveImsCallback = true;
+                    mPendingSuccessiveImsCallback = true;
                 }
                 final LatinIME latinIme = getOuterInstance();
-                executePendingImsCallback(latinIme, attribute, restarting);
-                latinIme.onStartInputInternal(attribute, restarting);
+                executePendingImsCallback(latinIme, editorInfo, restarting);
+                latinIme.onStartInputInternal(editorInfo, restarting);
             }
         }
 
-        public void onStartInputView(EditorInfo attribute, boolean restarting) {
-             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
-                 // Typically this is the second onStartInputView after orientation changed.
-                 resetPendingImsCallback();
-             } else {
-                 if (mPendingSuccesiveImsCallback) {
-                     // This is the first onStartInputView after orientation changed.
-                     mPendingSuccesiveImsCallback = false;
-                     resetPendingImsCallback();
-                     sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
-                             PENDING_IMS_CALLBACK_DURATION);
-                 }
-                 final LatinIME latinIme = getOuterInstance();
-                 executePendingImsCallback(latinIme, attribute, restarting);
-                 latinIme.onStartInputViewInternal(attribute, restarting);
-             }
+        public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+            if (hasMessages(MSG_PENDING_IMS_CALLBACK)
+                    && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) {
+                // Typically this is the second onStartInputView after orientation changed.
+                resetPendingImsCallback();
+            } else {
+                if (mPendingSuccessiveImsCallback) {
+                    // This is the first onStartInputView after orientation changed.
+                    mPendingSuccessiveImsCallback = false;
+                    resetPendingImsCallback();
+                    sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
+                            PENDING_IMS_CALLBACK_DURATION);
+                }
+                final LatinIME latinIme = getOuterInstance();
+                executePendingImsCallback(latinIme, editorInfo, restarting);
+                latinIme.onStartInputViewInternal(editorInfo, restarting);
+                mAppliedEditorInfo = editorInfo;
+            }
         }
 
         public void onFinishInputView(boolean finishingInput) {
@@ -470,6 +394,7 @@
             } else {
                 final LatinIME latinIme = getOuterInstance();
                 latinIme.onFinishInputViewInternal(finishingInput);
+                mAppliedEditorInfo = null;
             }
         }
 
@@ -485,24 +410,28 @@
         }
     }
 
+    public LatinIME() {
+        super();
+        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+    }
+
     @Override
     public void onCreate() {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
         mPrefs = prefs;
         LatinImeLogger.init(this, prefs);
-        LanguageSwitcherProxy.init(this, prefs);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.init(this, prefs);
+        }
         InputMethodManagerCompatWrapper.init(this);
         SubtypeSwitcher.init(this);
         KeyboardSwitcher.init(this, prefs);
-        AccessibilityUtils.init(this, prefs);
+        AccessibilityUtils.init(this);
 
         super.onCreate();
 
         mImm = InputMethodManagerCompatWrapper.getInstance();
-        mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
-        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
-        mVibrator = VibratorCompatWrapper.getInstance(this);
         mHandler.onCreate();
         DEBUG = LatinImeLogger.sDBG;
 
@@ -511,6 +440,10 @@
 
         loadSettings();
 
+        // TODO: remove the following when it's not needed by updateCorrectionMode() any more
+        mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+        updateCorrectionMode();
+
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -527,10 +460,9 @@
         // Register to receive ringer mode change and network state change.
         // Also receive installation and removal of a dictionary pack.
         final IntentFilter filter = new IntentFilter();
-        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
-        mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
 
         final IntentFilter packageFilter = new IntentFilter();
         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -547,50 +479,52 @@
     // Has to be package-visible for unit tests
     /* package */ void loadSettings() {
         if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
-        if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-        mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
+        final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
+            @Override
+            protected SettingsValues job(Resources res) {
+                return new SettingsValues(mPrefs, LatinIME.this);
+            }
+        };
+        mSettingsValues = job.runInLocale(mResources, mSubtypeSwitcher.getInputLocale());
+        mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues);
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
-        updateSoundEffectVolume();
-        updateKeypressVibrationDuration();
     }
 
     private void initSuggest() {
         final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
-        final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
+        final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
 
-        final Resources res = mResources;
-        final Locale savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale);
-        final ContactsDictionary oldContactsDictionary;
-        if (mSuggest != null) {
-            oldContactsDictionary = mSuggest.getContactsDictionary();
-            mSuggest.close();
-        } else {
-            oldContactsDictionary = null;
-        }
+        final Context context = this;
+        final RunInLocale<Void> job = new RunInLocale<Void>() {
+            @Override
+            protected Void job(Resources res) {
+                final ContactsDictionary oldContactsDictionary;
+                if (mSuggest != null) {
+                    oldContactsDictionary = mSuggest.getContactsDictionary();
+                    mSuggest.close();
+                } else {
+                    oldContactsDictionary = null;
+                }
 
-        int mainDicResId = Utils.getMainDictionaryResourceId(res);
-        mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
-        if (mSettingsValues.mAutoCorrectEnabled) {
-            mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
-        }
+                int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res);
+                mSuggest = new Suggest(context, mainDicResId, keyboardLocale);
+                if (mSettingsValues.mAutoCorrectEnabled) {
+                    mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+                }
 
-        mUserDictionary = new UserDictionary(this, localeStr);
-        mSuggest.setUserDictionary(mUserDictionary);
-        mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
+                mUserDictionary = new UserDictionary(context, localeStr);
+                mSuggest.setUserDictionary(mUserDictionary);
+                mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
 
-        resetContactsDictionary(oldContactsDictionary);
+                resetContactsDictionary(oldContactsDictionary);
 
-        mUserUnigramDictionary
-                = new UserUnigramDictionary(this, this, localeStr, Suggest.DIC_USER_UNIGRAM);
-        mSuggest.setUserUnigramDictionary(mUserUnigramDictionary);
-
-        mUserBigramDictionary
-                = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER_BIGRAM);
-        mSuggest.setUserBigramDictionary(mUserBigramDictionary);
-
-        updateCorrectionMode();
-
-        LocaleUtils.setSystemLocale(res, savedLocale);
+                mUserHistoryDictionary
+                    = new UserHistoryDictionary(context, localeStr, Suggest.DIC_USER_HISTORY);
+                mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
+                return null;
+            }
+        };
+        job.runInLocale(mResources, keyboardLocale);
     }
 
     /**
@@ -626,9 +560,8 @@
     }
 
     /* package private */ void resetSuggestMainDict() {
-        final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
-        final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
-        int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+        final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
+        int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(mResources);
         mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
     }
 
@@ -640,7 +573,6 @@
         }
         unregisterReceiver(mReceiver);
         unregisterReceiver(mDictionaryPackInstallReceiver);
-        mVoiceProxy.destroy();
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -649,25 +581,17 @@
     @Override
     public void onConfigurationChanged(Configuration conf) {
         mSubtypeSwitcher.onConfigurationChanged(conf);
-        mComposingStateManager.onFinishComposingText();
         // If orientation changed while predicting, commit the change
         if (mDisplayOrientation != conf.orientation) {
             mDisplayOrientation = conf.orientation;
             mHandler.startOrientationChanging();
             final InputConnection ic = getCurrentInputConnection();
-            commitTyped(ic);
+            commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
             if (ic != null) ic.finishComposingText(); // For voice input
             if (isShowingOptionDialog())
                 mOptionsDialog.dismiss();
         }
-
-        mConfigurationChanging = true;
         super.onConfigurationChanged(conf);
-        mVoiceProxy.onConfigurationChanged(conf);
-        mConfigurationChanging = false;
-
-        // This will work only when the subtype is not supported.
-        LanguageSwitcherProxy.onConfigurationChanged(conf);
     }
 
     @Override
@@ -697,13 +621,13 @@
     }
 
     @Override
-    public void onStartInput(EditorInfo attribute, boolean restarting) {
-        mHandler.onStartInput(attribute, restarting);
+    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+        mHandler.onStartInput(editorInfo, restarting);
     }
 
     @Override
-    public void onStartInputView(EditorInfo attribute, boolean restarting) {
-        mHandler.onStartInputView(attribute, restarting);
+    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+        mHandler.onStartInputView(editorInfo, restarting);
     }
 
     @Override
@@ -716,20 +640,44 @@
         mHandler.onFinishInput();
     }
 
-    private void onStartInputInternal(EditorInfo attribute, boolean restarting) {
-        super.onStartInput(attribute, restarting);
+    @Override
+    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
+        SubtypeSwitcher.getInstance().updateSubtype(subtype);
     }
 
-    private void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
-        super.onStartInputView(attribute, restarting);
+    private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
+        super.onStartInput(editorInfo, restarting);
+    }
+
+    private void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+        super.onStartInputView(editorInfo, restarting);
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         LatinKeyboardView inputView = switcher.getKeyboardView();
 
-        if (DEBUG) {
-            Log.d(TAG, "onStartInputView: attribute:" + ((attribute == null) ? "none"
-                    : String.format("inputType=0x%08x imeOptions=0x%08x",
-                            attribute.inputType, attribute.imeOptions)));
+        if (editorInfo == null) {
+            Log.e(TAG, "Null EditorInfo in onStartInputView()");
+            if (LatinImeLogger.sDBG) {
+                throw new NullPointerException("Null EditorInfo in onStartInputView()");
+            }
+            return;
         }
+        if (DEBUG) {
+            Log.d(TAG, "onStartInputView: editorInfo:"
+                    + String.format("inputType=0x%08x imeOptions=0x%08x",
+                            editorInfo.inputType, editorInfo.imeOptions));
+        }
+        if (StringUtils.inPrivateImeOptions(null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) {
+            Log.w(TAG, "Deprecated private IME option specified: "
+                    + editorInfo.privateImeOptions);
+            Log.w(TAG, "Use " + getPackageName() + "." + IME_OPTION_NO_MICROPHONE + " instead");
+        }
+        if (StringUtils.inPrivateImeOptions(getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) {
+            Log.w(TAG, "Deprecated private IME option specified: "
+                    + editorInfo.privateImeOptions);
+            Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
+        }
+
+        LatinImeLogger.onStartInputView(editorInfo);
         // In landscape mode, this method gets called without the input view being created.
         if (inputView == null) {
             return;
@@ -738,47 +686,35 @@
         // Forward this event to the accessibility utilities, if enabled.
         final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
         if (accessUtils.isTouchExplorationEnabled()) {
-            accessUtils.onStartInputViewInternal(attribute, restarting);
+            accessUtils.onStartInputViewInternal(editorInfo, restarting);
         }
 
         mSubtypeSwitcher.updateParametersOnStartInputView();
 
-        TextEntryState.reset();
-
-        // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
-        // know now whether this is a password text field, because we need to know now whether we
-        // want to enable the voice button.
-        final VoiceProxy voiceIme = mVoiceProxy;
-        final int inputType = (attribute != null) ? attribute.inputType : 0;
-        voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(inputType)
-                || InputTypeCompatUtils.isVisiblePasswordInputType(inputType));
-
         // The EditorInfo might have a flag that affects fullscreen mode.
         // Note: This call should be done by InputMethodService?
         updateFullscreenMode();
-        initializeInputAttributes(attribute);
+        mLastSelectionStart = editorInfo.initialSelStart;
+        mLastSelectionEnd = editorInfo.initialSelEnd;
+        mInputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+        mApplicationSpecifiedCompletions = null;
 
         inputView.closing();
         mEnteredText = null;
-        mComposingStringBuilder.setLength(0);
-        mHasUncommittedTypedChars = false;
+        resetComposingState(true /* alsoResetLastComposedWord */);
         mDeleteCount = 0;
-        mJustAddedMagicSpace = false;
-        mJustReplacedDoubleSpace = false;
+        mSpaceState = SPACE_STATE_NONE;
 
         loadSettings();
         updateCorrectionMode();
-        updateSuggestionVisibility(mPrefs, mResources);
+        updateSuggestionVisibility(mResources);
 
         if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
             mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
         }
-        mVoiceProxy.loadSettings(attribute, mPrefs);
-        // This will work only when the subtype is not supported.
-        LanguageSwitcherProxy.loadSettings();
 
         if (mSubtypeSwitcher.isKeyboardMode()) {
-            switcher.loadKeyboard(attribute, mSettingsValues);
+            switcher.loadKeyboard(editorInfo, mSettingsValues);
         }
 
         if (mSuggestionsView != null)
@@ -787,83 +723,15 @@
                 isSuggestionsStripVisible(), /* needsInputViewShown */ false);
         // Delay updating suggestions because keyboard input view may not be shown at this point.
         mHandler.postUpdateSuggestions();
+        mHandler.cancelDoubleSpacesTimer();
 
         inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
                 mSettingsValues.mKeyPreviewPopupDismissDelay);
         inputView.setProximityCorrectionEnabled(true);
 
-        voiceIme.onStartInputView(inputView.getWindowToken());
-
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
 
-    private void initializeInputAttributes(EditorInfo attribute) {
-        if (attribute == null)
-            return;
-        final int inputType = attribute.inputType;
-        if (inputType == InputType.TYPE_NULL) {
-            // TODO: We should honor TYPE_NULL specification.
-            Log.i(TAG, "InputType.TYPE_NULL is specified");
-        }
-        final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
-        final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-        if (inputClass == 0) {
-            Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x imeOptions=0x%08x",
-                    inputType, attribute.imeOptions));
-        }
-
-        mInsertSpaceOnPickSuggestionManually = false;
-        mInputTypeNoAutoCorrect = false;
-        mIsSettingsSuggestionStripOn = false;
-        mApplicationSpecifiedCompletionOn = false;
-        mApplicationSpecifiedCompletions = null;
-
-        if (inputClass == InputType.TYPE_CLASS_TEXT) {
-            mIsSettingsSuggestionStripOn = true;
-            // Make sure that passwords are not displayed in {@link SuggestionsView}.
-            if (InputTypeCompatUtils.isPasswordInputType(inputType)
-                    || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
-                mIsSettingsSuggestionStripOn = false;
-            }
-            if (InputTypeCompatUtils.isEmailVariation(variation)
-                    || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
-                // The point in turning this off is that we don't want to insert a space after
-                // a name when filling a form: we can't delete trailing spaces when changing fields
-                mInsertSpaceOnPickSuggestionManually = false;
-            } else {
-                mInsertSpaceOnPickSuggestionManually = true;
-            }
-            if (InputTypeCompatUtils.isEmailVariation(variation)) {
-                mIsSettingsSuggestionStripOn = false;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
-                mIsSettingsSuggestionStripOn = false;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
-                mIsSettingsSuggestionStripOn = false;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
-                // If it's a browser edit field and auto correct is not ON explicitly, then
-                // disable auto correction, but keep suggestions on.
-                if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
-                    mInputTypeNoAutoCorrect = true;
-                }
-            }
-
-            // If NO_SUGGESTIONS is set, don't do prediction.
-            if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
-                mIsSettingsSuggestionStripOn = false;
-                mInputTypeNoAutoCorrect = true;
-            }
-            // If it's not multiline and the autoCorrect flag is not set, then don't correct
-            if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
-                    && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
-                mInputTypeNoAutoCorrect = true;
-            }
-            if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
-                mIsSettingsSuggestionStripOn = false;
-                mApplicationSpecifiedCompletionOn = isFullscreenMode();
-            }
-        }
-    }
-
     @Override
     public void onWindowHidden() {
         super.onWindowHidden();
@@ -876,12 +744,9 @@
 
         LatinImeLogger.commit();
 
-        mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
-
         KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.closing();
-        if (mUserUnigramDictionary != null) mUserUnigramDictionary.flushPendingWrites();
-        if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
+        if (mUserHistoryDictionary != null) mUserHistoryDictionary.flushPendingWrites();
     }
 
     private void onFinishInputViewInternal(boolean finishingInput) {
@@ -894,18 +759,25 @@
     }
 
     @Override
-    public void onUpdateExtractedText(int token, ExtractedText text) {
-        super.onUpdateExtractedText(token, text);
-        mVoiceProxy.showPunctuationHintIfNecessary();
-    }
-
-    @Override
     public void onUpdateSelection(int oldSelStart, int oldSelEnd,
             int newSelStart, int newSelEnd,
-            int candidatesStart, int candidatesEnd) {
+            int composingSpanStart, int composingSpanEnd) {
         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
-                candidatesStart, candidatesEnd);
+                composingSpanStart, composingSpanEnd);
 
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            if (ResearchLogger.UnsLogGroup.ON_UPDATE_SELECTION.isEnabled) {
+                final String s = "onUpdateSelection: oss=" + oldSelStart
+                    + ", ose=" + oldSelEnd
+                    + ", lss=" + mLastSelectionStart
+                    + ", lse=" + mLastSelectionEnd
+                    + ", nss=" + newSelStart
+                    + ", nse=" + newSelEnd
+                    + ", cs=" + composingSpanStart
+                    + ", ce=" + composingSpanEnd;
+                ResearchLogger.logUnstructured(ResearchLogger.UnsLogGroup.ON_UPDATE_SELECTION, s);
+            }
+        }
         if (DEBUG) {
             Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
                     + ", ose=" + oldSelEnd
@@ -913,51 +785,56 @@
                     + ", lse=" + mLastSelectionEnd
                     + ", nss=" + newSelStart
                     + ", nse=" + newSelEnd
-                    + ", cs=" + candidatesStart
-                    + ", ce=" + candidatesEnd);
+                    + ", cs=" + composingSpanStart
+                    + ", ce=" + composingSpanEnd);
         }
 
-        mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart);
-
-        // If the current selection in the text view changes, we should
-        // clear whatever candidate text we have.
-        final boolean selectionChanged = (newSelStart != candidatesEnd
-                || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
-        final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
+        // TODO: refactor the following code to be less contrived.
+        // "newSelStart != composingSpanEnd" || "newSelEnd != composingSpanEnd" means
+        // that the cursor is not at the end of the composing span, or there is a selection.
+        // "mLastSelectionStart != newSelStart" means that the cursor is not in the same place
+        // as last time we were called (if there is a selection, it means the start hasn't
+        // changed, so it's the end that did).
+        final boolean selectionChanged = (newSelStart != composingSpanEnd
+                || newSelEnd != composingSpanEnd) && mLastSelectionStart != newSelStart;
+        // if composingSpanStart and composingSpanEnd are -1, it means there is no composing
+        // span in the view - we can use that to narrow down whether the cursor was moved
+        // by us or not. If we are composing a word but there is no composing span, then
+        // we know for sure the cursor moved while we were composing and we should reset
+        // the state.
+        final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1;
         if (!mExpectingUpdateSelection) {
-            if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
-                    || mVoiceProxy.isVoiceInputHighlighted())
-                    && (selectionChanged || candidatesCleared)) {
-                mComposingStringBuilder.setLength(0);
-                mHasUncommittedTypedChars = false;
-                TextEntryState.reset();
-                updateSuggestions();
-                final InputConnection ic = getCurrentInputConnection();
-                if (ic != null) {
-                    ic.finishComposingText();
-                }
-                mComposingStateManager.onFinishComposingText();
-                mVoiceProxy.setVoiceInputHighlighted(false);
-            } else if (!mHasUncommittedTypedChars) {
-                TextEntryState.reset();
-                updateSuggestions();
+            // TAKE CARE: there is a race condition when we enter this test even when the user
+            // did not explicitly move the cursor. This happens when typing fast, where two keys
+            // turn this flag on in succession and both onUpdateSelection() calls arrive after
+            // the second one - the first call successfully avoids this test, but the second one
+            // enters. For the moment we rely on noComposingSpan to further reduce the impact.
+
+            // TODO: the following is probably better done in resetEntireInputState().
+            // it should only happen when the cursor moved, and the very purpose of the
+            // test below is to narrow down whether this happened or not. Likewise with
+            // the call to postUpdateShiftState.
+            // We set this to NONE because after a cursor move, we don't want the space
+            // state-related special processing to kick in.
+            mSpaceState = SPACE_STATE_NONE;
+
+            if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) {
+                resetEntireInputState();
             }
-            mJustAddedMagicSpace = false; // The user moved the cursor.
-            mJustReplacedDoubleSpace = false;
+
+            mHandler.postUpdateShiftState();
         }
         mExpectingUpdateSelection = false;
-        mHandler.postUpdateShiftKeyState();
+        // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
+        // here. It would probably be too expensive to call directly here but we may want to post a
+        // message to delay it. The point would be to unify behavior between backspace to the
+        // end of a word and manually put the pointer at the end of the word.
 
         // Make a note of the cursor position
         mLastSelectionStart = newSelStart;
         mLastSelectionEnd = newSelEnd;
     }
 
-    public void setLastSelection(int start, int end) {
-        mLastSelectionStart = start;
-        mLastSelectionEnd = end;
-    }
-
     /**
      * This is called when the user has clicked on the extracted text view,
      * when running in fullscreen mode.  The default implementation hides
@@ -999,7 +876,6 @@
             mOptionsDialog.dismiss();
             mOptionsDialog = null;
         }
-        mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
         super.hideWindow();
     }
 
@@ -1013,20 +889,30 @@
                 }
             }
         }
-        if (mApplicationSpecifiedCompletionOn) {
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn) {
             mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
             if (applicationSpecifiedCompletions == null) {
                 clearSuggestions();
                 return;
             }
 
-            SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                    .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions)
-                    .setTypedWordValid(false)
-                    .setHasMinimalSuggestion(false);
+            final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
+                    SuggestedWords.getFromApplicationSpecifiedCompletions(
+                            applicationSpecifiedCompletions);
+            final SuggestedWords suggestedWords = new SuggestedWords(
+                    applicationSuggestedWords,
+                    false /* typedWordValid */,
+                    false /* hasAutoCorrectionCandidate */,
+                    false /* allowsToBeAutoCorrected */,
+                    false /* isPunctuationSuggestions */,
+                    false /* isObsoleteSuggestions */);
             // When in fullscreen mode, show completions generated by the application
-            setSuggestions(builder.build());
-            mBestWord = null;
+            final boolean isAutoCorrection = false;
+            setSuggestions(suggestedWords, isAutoCorrection);
+            setAutoCorrectionIndicator(isAutoCorrection);
+            // TODO: is this the right thing to do? What should we auto-correct to in
+            // this case? This says to keep whatever the user typed.
+            mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
             setSuggestionStripShown(true);
         }
     }
@@ -1034,8 +920,10 @@
     private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
         // TODO: Modify this if we support suggestions with hard keyboard
         if (onEvaluateInputViewShown() && mSuggestionsContainer != null) {
+            final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+            final boolean inputViewShown = (keyboardView != null) ? keyboardView.isShown() : false;
             final boolean shouldShowSuggestions = shown
-                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
+                    && (needsInputViewShown ? inputViewShown : true);
             if (isFullscreenMode()) {
                 mSuggestionsContainer.setVisibility(
                         shouldShowSuggestions ? View.VISIBLE : View.GONE);
@@ -1089,7 +977,8 @@
         final int extraHeight = extractHeight + backingHeight + suggestionsHeight;
         int touchY = extraHeight;
         // Need to set touchable region only if input view is being shown
-        if (mKeyboardSwitcher.isInputViewShown()) {
+        final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+        if (keyboardView != null && keyboardView.isShown()) {
             if (mSuggestionsContainer.getVisibility() == View.VISIBLE) {
                 touchY -= suggestionsHeight;
             }
@@ -1097,11 +986,8 @@
             final int touchHeight = inputView.getHeight() + extraHeight
                     // Extend touchable region below the keyboard.
                     + EXTENDED_TOUCHABLE_REGION_HEIGHT;
-            if (DEBUG) {
-                Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth
-                        + " height=" + touchHeight);
-            }
-            setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight);
+            outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
+            outInsets.touchableRegion.set(0, touchY, touchWidth, touchHeight);
         }
         outInsets.contentTopInsets = touchY;
         outInsets.visibleTopInsets = touchY;
@@ -1109,8 +995,10 @@
 
     @Override
     public boolean onEvaluateFullscreenMode() {
-        return super.onEvaluateFullscreenMode()
-                && mResources.getBoolean(R.bool.config_use_fullscreen_mode);
+        // Reread resource value here, because this method is called by framework anytime as needed.
+        final boolean isFullscreenModeAllowed =
+                mSettingsValues.isFullscreenModeAllowed(getResources());
+        return super.onEvaluateFullscreenMode() && isFullscreenModeAllowed;
     }
 
     @Override
@@ -1148,9 +1036,11 @@
         case KeyEvent.KEYCODE_DPAD_UP:
         case KeyEvent.KEYCODE_DPAD_LEFT:
         case KeyEvent.KEYCODE_DPAD_RIGHT:
+            final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+            final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
             // Enable shift key and DPAD to do selections
-            if (mKeyboardSwitcher.isInputViewShown()
-                    && mKeyboardSwitcher.isShiftedOrShiftLocked()) {
+            if ((keyboardView != null && keyboardView.isShown())
+                    && (keyboard != null && keyboard.isShiftedOrShiftLocked())) {
                 KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(),
                         event.getAction(), event.getKeyCode(), event.getRepeatCount(),
                         event.getDeviceId(), event.getScanCode(),
@@ -1165,17 +1055,35 @@
         return super.onKeyUp(keyCode, event);
     }
 
-    public void commitTyped(final InputConnection ic) {
-        if (!mHasUncommittedTypedChars) return;
-        mHasUncommittedTypedChars = false;
-        if (mComposingStringBuilder.length() > 0) {
+    // This will reset the whole input state to the starting state. It will clear
+    // the composing word, reset the last composed word, tell the inputconnection
+    // and the composingStateManager about it.
+    private void resetEntireInputState() {
+        resetComposingState(true /* alsoResetLastComposedWord */);
+        updateSuggestions();
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            ic.finishComposingText();
+        }
+    }
+
+    private void resetComposingState(final boolean alsoResetLastComposedWord) {
+        mWordComposer.reset();
+        if (alsoResetLastComposedWord)
+            mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+    }
+
+    public void commitTyped(final InputConnection ic, final int separatorCode) {
+        if (!mWordComposer.isComposingWord()) return;
+        final CharSequence typedWord = mWordComposer.getTypedWord();
+        if (typedWord.length() > 0) {
+            mLastComposedWord = mWordComposer.commitWord(
+                    LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
+                    separatorCode);
             if (ic != null) {
-                ic.commitText(mComposingStringBuilder, 1);
+                ic.commitText(typedWord, 1);
             }
-            mCommittedLength = mComposingStringBuilder.length();
-            TextEntryState.acceptedTyped(mComposingStringBuilder);
-            addToUserUnigramAndBigramDictionaries(mComposingStringBuilder,
-                    UserUnigramDictionary.FREQUENCY_FOR_TYPED);
+            addToUserHistoryDictionary(typedWord);
         }
         updateSuggestions();
     }
@@ -1190,60 +1098,41 @@
         return false;
     }
 
-    private void swapSwapperAndSpace() {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
+    // "ic" may be null
+    private void swapSwapperAndSpaceWhileInBatchEdit(final InputConnection ic) {
+        if (null == ic) return;
         CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
         // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
         if (lastTwo != null && lastTwo.length() == 2
                 && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
-            ic.beginBatchEdit();
             ic.deleteSurroundingText(2, 0);
             ic.commitText(lastTwo.charAt(1) + " ", 1);
-            ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
         }
     }
 
-    private void maybeDoubleSpace() {
-        if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
+    private boolean maybeDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
+        if (mCorrectionMode == Suggest.CORRECTION_NONE) return false;
+        if (ic == null) return false;
         final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
-                && Utils.canBeFollowedByPeriod(lastThree.charAt(0))
+                && StringUtils.canBeFollowedByPeriod(lastThree.charAt(0))
                 && lastThree.charAt(1) == Keyboard.CODE_SPACE
                 && lastThree.charAt(2) == Keyboard.CODE_SPACE
                 && mHandler.isAcceptingDoubleSpaces()) {
             mHandler.cancelDoubleSpacesTimer();
-            ic.beginBatchEdit();
             ic.deleteSurroundingText(2, 0);
             ic.commitText(". ", 1);
-            ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
-            mJustReplacedDoubleSpace = true;
-        } else {
-            mHandler.startDoubleSpacesTimer();
+            return true;
         }
+        return false;
     }
 
-    // "ic" must not null
-    private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
-        // When the text's first character is '.', remove the previous period
-        // if there is one.
-        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
-        if (lastOne != null && lastOne.length() == 1
-                && lastOne.charAt(0) == Keyboard.CODE_PERIOD
-                && text.charAt(0) == Keyboard.CODE_PERIOD) {
-            ic.deleteSurroundingText(1, 0);
-        }
-    }
-
-    private void removeTrailingSpace() {
-        final InputConnection ic = getCurrentInputConnection();
+    // "ic" may be null
+    private static void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) {
         if (ic == null) return;
-
-        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
             ic.deleteSurroundingText(1, 0);
@@ -1259,38 +1148,32 @@
         return true;
     }
 
-    private boolean isAlphabet(int code) {
-        if (Character.isLetter(code)) {
-            return true;
-        } else {
-            return false;
-        }
+    private static boolean isAlphabet(int code) {
+        return Character.isLetter(code);
     }
 
     private void onSettingsKeyPressed() {
         if (isShowingOptionDialog()) return;
-        if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
-            showSubtypeSelectorAndSettings();
-        } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, false /* exclude aux subtypes */)) {
-            showOptionsMenu();
-        } else {
-            launchSettings();
-        }
+        showSubtypeSelectorAndSettings();
     }
 
     // Virtual codes representing custom requests.  These are used in onCustomRequest() below.
     public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
+    public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK = 2;
 
     @Override
     public boolean onCustomRequest(int requestCode) {
         if (isShowingOptionDialog()) return false;
         switch (requestCode) {
         case CODE_SHOW_INPUT_METHOD_PICKER:
-            if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, true /* include aux subtypes */)) {
+            if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 mImm.showInputMethodPicker();
                 return true;
             }
             return false;
+        case CODE_HAPTIC_AND_AUDIO_FEEDBACK:
+            hapticAndAudioFeedback(Keyboard.CODE_UNSPECIFIED);
+            return true;
         }
         return false;
     }
@@ -1299,108 +1182,173 @@
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
+    private static int getActionId(Keyboard keyboard) {
+        return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
+    }
+
+    private void performEditorAction(int actionId) {
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            ic.performEditorAction(actionId);
+        }
+    }
+
+    private void handleLanguageSwitchKey() {
+        final boolean includesOtherImes = mSettingsValues.mIncludesOtherImesInLanguageSwitchList;
+        final IBinder token = getWindow().getWindow().getAttributes().token;
+        if (mShouldSwitchToLastSubtype) {
+            final InputMethodSubtype lastSubtype = mImm.getLastInputMethodSubtype();
+            final boolean lastSubtypeBelongsToThisIme = SubtypeUtils.checkIfSubtypeBelongsToThisIme(
+                    this, lastSubtype);
+            if ((includesOtherImes || lastSubtypeBelongsToThisIme)
+                    && mImm.switchToLastInputMethod(token)) {
+                mShouldSwitchToLastSubtype = false;
+            } else {
+                mImm.switchToNextInputMethod(token, !includesOtherImes);
+                mShouldSwitchToLastSubtype = true;
+            }
+        } else {
+            mImm.switchToNextInputMethod(token, !includesOtherImes);
+        }
+    }
+
+    private void sendKeyCodePoint(int code) {
+        // TODO: Remove this special handling of digit letters.
+        // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
+        if (code >= '0' && code <= '9') {
+            super.sendKeyChar((char)code);
+            return;
+        }
+
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            final String text = new String(new int[] { code }, 0, 1);
+            ic.commitText(text, text.length());
+        }
+    }
+
     // Implementation of {@link KeyboardActionListener}.
     @Override
-    public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+    public void onCodeInput(int primaryCode, int x, int y) {
         final long when = SystemClock.uptimeMillis();
         if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
             mDeleteCount = 0;
         }
         mLastKeyTime = when;
+
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            if (ResearchLogger.sIsLogging) {
+                ResearchLogger.getInstance().logKeyEvent(primaryCode, x, y);
+            }
+        }
+
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
-        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
-        final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace;
-        mJustReplacedDoubleSpace = false;
-        boolean shouldStartKeyTypedTimer = true;
+        // The space state depends only on the last character pressed and its own previous
+        // state. Here, we revert the space state to neutral if the key is actually modifying
+        // the input contents (any non-shift key), which is what we should do for
+        // all inputs that do not result in a special state. Each character handling is then
+        // free to override the state as they see fit.
+        final int spaceState = mSpaceState;
+        if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false;
+
+        // TODO: Consolidate the double space timer, mLastKeyTime, and the space state.
+        if (primaryCode != Keyboard.CODE_SPACE) {
+            mHandler.cancelDoubleSpacesTimer();
+        }
+
+        boolean didAutoCorrect = false;
         switch (primaryCode) {
         case Keyboard.CODE_DELETE:
-            handleBackspace(lastStateOfJustReplacedDoubleSpace);
+            mSpaceState = SPACE_STATE_NONE;
+            handleBackspace(spaceState);
             mDeleteCount++;
             mExpectingUpdateSelection = true;
-            LatinImeLogger.logOnDelete();
+            mShouldSwitchToLastSubtype = true;
+            LatinImeLogger.logOnDelete(x, y);
             break;
         case Keyboard.CODE_SHIFT:
-            // Shift key is handled in onPress() when device has distinct multi-touch panel.
-            if (!distinctMultiTouch) {
-                switcher.toggleShift();
-            }
-            shouldStartKeyTypedTimer = false;
-            break;
         case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
-            // Symbol key is handled in onPress() when device has distinct multi-touch panel.
-            if (!distinctMultiTouch) {
-                switcher.changeKeyboardMode();
-            }
-            shouldStartKeyTypedTimer = false;
-            break;
-        case Keyboard.CODE_CANCEL:
-            if (!isShowingOptionDialog()) {
-                handleClose();
-            }
+            // Shift and symbol key is handled in onPressKey() and onReleaseKey().
             break;
         case Keyboard.CODE_SETTINGS:
-            if (!mHandler.isIgnoringSpecialKey()) {
-                onSettingsKeyPressed();
-            }
-            shouldStartKeyTypedTimer = false;
-            break;
-        case Keyboard.CODE_CAPSLOCK:
-            switcher.toggleCapsLock();
-            //$FALL-THROUGH$
-        case Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY:
-            // Dummy code for haptic and audio feedbacks.
-            vibrate();
-            playKeyClick(primaryCode);
+            onSettingsKeyPressed();
             break;
         case Keyboard.CODE_SHORTCUT:
-            if (!mHandler.isIgnoringSpecialKey()) {
-                mSubtypeSwitcher.switchToShortcutIME();
-            }
-            shouldStartKeyTypedTimer = false;
+            mSubtypeSwitcher.switchToShortcutIME();
             break;
-        case Keyboard.CODE_TAB:
-            handleTab();
-            // There are two cases for tab. Either we send a "next" event, that may change the
-            // focus but will never move the cursor. Or, we send a real tab keycode, which some
-            // applications may accept or ignore, and we don't know whether this will move the
-            // cursor or not. So actually, we don't really know.
-            // So to go with the safer option, we'd rather behave as if the user moved the
-            // cursor when they didn't than the opposite. We also expect that most applications
-            // will actually use tab only for focus movement.
-            // To sum it up: do not update mExpectingUpdateSelection here.
+        case Keyboard.CODE_ACTION_ENTER:
+            performEditorAction(getActionId(switcher.getKeyboard()));
+            break;
+        case Keyboard.CODE_ACTION_NEXT:
+            performEditorAction(EditorInfo.IME_ACTION_NEXT);
+            break;
+        case Keyboard.CODE_ACTION_PREVIOUS:
+            performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+            break;
+        case Keyboard.CODE_LANGUAGE_SWITCH:
+            handleLanguageSwitchKey();
             break;
         default:
+            mSpaceState = SPACE_STATE_NONE;
             if (mSettingsValues.isWordSeparator(primaryCode)) {
-                handleSeparator(primaryCode, x, y);
+                didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
             } else {
-                handleCharacter(primaryCode, keyCodes, x, y);
+                final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+                if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
+                    handleCharacter(primaryCode, x, y, spaceState);
+                } else {
+                    handleCharacter(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE,
+                            spaceState);
+                }
             }
             mExpectingUpdateSelection = true;
+            mShouldSwitchToLastSubtype = true;
             break;
         }
-        switcher.onKey(primaryCode);
+        switcher.onCodeInput(primaryCode);
         // Reset after any single keystroke
+        if (!didAutoCorrect)
+            mLastComposedWord.deactivate();
         mEnteredText = null;
-        if (shouldStartKeyTypedTimer) {
-            mHandler.startKeyTypedTimer();
-        }
     }
 
     @Override
     public void onTextInput(CharSequence text) {
-        mVoiceProxy.commitVoiceInput();
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         ic.beginBatchEdit();
-        commitTyped(ic);
-        maybeRemovePreviousPeriod(ic, text);
+        commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
+        text = specificTldProcessingOnTextInput(ic, text);
+        if (SPACE_STATE_PHANTOM == mSpaceState) {
+            sendKeyCodePoint(Keyboard.CODE_SPACE);
+        }
         ic.commitText(text, 1);
         ic.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
-        mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
-        mJustAddedMagicSpace = false;
+        mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
+        mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
-        mHandler.startKeyTypedTimer();
+        resetComposingState(true /* alsoResetLastComposedWord */);
+    }
+
+    // ic may not be null
+    private CharSequence specificTldProcessingOnTextInput(final InputConnection ic,
+            final CharSequence text) {
+        if (text.length() <= 1 || text.charAt(0) != Keyboard.CODE_PERIOD
+                || !Character.isLetter(text.charAt(1))) {
+            // Not a tld: do nothing.
+            return text;
+        }
+        // We have a TLD (or something that looks like this): make sure we don't add
+        // a space even if currently in phantom mode.
+        mSpaceState = SPACE_STATE_NONE;
+        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+        if (lastOne != null && lastOne.length() == 1
+                && lastOne.charAt(0) == Keyboard.CODE_PERIOD) {
+            return text.subSequence(1, text.length());
+        } else {
+            return text;
+        }
     }
 
     @Override
@@ -1409,249 +1357,274 @@
         mKeyboardSwitcher.onCancelInput();
     }
 
-    private void handleBackspace(boolean justReplacedDoubleSpace) {
-        if (mVoiceProxy.logAndRevertVoiceInput()) return;
-
+    private void handleBackspace(final int spaceState) {
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         ic.beginBatchEdit();
+        handleBackspaceWhileInBatchEdit(spaceState, ic);
+        ic.endBatchEdit();
+    }
 
-        mVoiceProxy.handleBackspace();
+    // "ic" may not be null.
+    private void handleBackspaceWhileInBatchEdit(final int spaceState, final InputConnection ic) {
+        // In many cases, we may have to put the keyboard in auto-shift state again.
+        mHandler.postUpdateShiftState();
 
-        final boolean deleteChar = !mHasUncommittedTypedChars;
-        if (mHasUncommittedTypedChars) {
-            final int length = mComposingStringBuilder.length();
+        if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
+            // Cancel multi-character input: remove the text we just entered.
+            // This is triggered on backspace after a key that inputs multiple characters,
+            // like the smiley key or the .com key.
+            ic.deleteSurroundingText(mEnteredText.length(), 0);
+            // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
+            // In addition we know that spaceState is false, and that we should not be
+            // reverting any autocorrect at this point. So we can safely return.
+            return;
+        }
+
+        if (mWordComposer.isComposingWord()) {
+            final int length = mWordComposer.size();
             if (length > 0) {
-                mComposingStringBuilder.delete(length - 1, length);
                 mWordComposer.deleteLast();
-                final CharSequence textWithUnderline =
-                        mComposingStateManager.isAutoCorrectionIndicatorOn()
-                                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                            this, mComposingStringBuilder)
-                                : mComposingStringBuilder;
-                ic.setComposingText(textWithUnderline, 1);
-                if (mComposingStringBuilder.length() == 0) {
-                    mHasUncommittedTypedChars = false;
-                }
-                if (1 == length) {
-                    // 1 == length means we are about to erase the last character of the word,
-                    // so we can show bigrams.
+                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+                // If we have deleted the last remaining character of a word, then we are not
+                // isComposingWord() any more.
+                if (!mWordComposer.isComposingWord()) {
+                    // Not composing word any more, so we can show bigrams.
                     mHandler.postUpdateBigramPredictions();
                 } else {
-                    // length > 1, so we still have letters to deduce a suggestion from.
+                    // Still composing a word, so we still have letters to deduce a suggestion from.
                     mHandler.postUpdateSuggestions();
                 }
             } else {
                 ic.deleteSurroundingText(1, 0);
             }
-        }
-        mHandler.postUpdateShiftKeyState();
-
-        TextEntryState.backspace();
-        if (TextEntryState.isUndoCommit()) {
-            revertLastWord(ic);
-            ic.endBatchEdit();
-            return;
-        }
-        if (justReplacedDoubleSpace) {
-            if (revertDoubleSpace(ic)) {
-                ic.endBatchEdit();
+        } else {
+            if (mLastComposedWord.canRevertCommit()) {
+                Utils.Stats.onAutoCorrectionCancellation();
+                revertCommit(ic);
                 return;
             }
-        }
-
-        if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
-            ic.deleteSurroundingText(mEnteredText.length(), 0);
-        } else if (deleteChar) {
-            if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
-                // Go back to the suggestion mode if the user canceled the
-                // "Touch again to save".
-                // NOTE: In gerenal, we don't revert the word when backspacing
-                // from a manual suggestion pick.  We deliberately chose a
-                // different behavior only in the case of picking the first
-                // suggestion (typed word).  It's intentional to have made this
-                // inconsistent with backspacing after selecting other suggestions.
-                revertLastWord(ic);
-            } else {
-                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
-                if (mDeleteCount > DELETE_ACCELERATE_AT) {
-                    sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+            if (SPACE_STATE_DOUBLE == spaceState) {
+                if (revertDoubleSpaceWhileInBatchEdit(ic)) {
+                    // No need to reset mSpaceState, it has already be done (that's why we
+                    // receive it as a parameter)
+                    return;
                 }
-            }
-        }
-        ic.endBatchEdit();
-    }
-
-    private void handleTab() {
-        final int imeOptions = getCurrentInputEditorInfo().imeOptions;
-        if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
-                && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) {
-            sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
-            return;
-        }
-
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null)
-            return;
-
-        // True if keyboard is in either chording shift or manual temporary upper case mode.
-        final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
-        if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
-                && !isManualTemporaryUpperCase) {
-            EditorInfoCompatUtils.performEditorActionNext(ic);
-        } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)
-                && isManualTemporaryUpperCase) {
-            EditorInfoCompatUtils.performEditorActionPrevious(ic);
-        }
-    }
-
-    private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
-        mVoiceProxy.handleCharacter();
-
-        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
-            removeTrailingSpace();
-        }
-
-        int code = primaryCode;
-        if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
-                && isSuggestionsRequested() && !isCursorTouchingWord()) {
-            if (!mHasUncommittedTypedChars) {
-                mHasUncommittedTypedChars = true;
-                mComposingStringBuilder.setLength(0);
-                mWordComposer.reset();
-                clearSuggestions();
-                mComposingStateManager.onFinishComposingText();
-            }
-        }
-        final KeyboardSwitcher switcher = mKeyboardSwitcher;
-        if (switcher.isShiftedOrShiftLocked()) {
-            if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
-                    || keyCodes[0] > Character.MAX_CODE_POINT) {
-                return;
-            }
-            code = keyCodes[0];
-            if (switcher.isAlphabetMode() && Character.isLowerCase(code)) {
-                // In some locales, such as Turkish, Character.toUpperCase() may return a wrong
-                // character because it doesn't take care of locale.
-                final String upperCaseString = new String(new int[] {code}, 0, 1)
-                        .toUpperCase(mSubtypeSwitcher.getInputLocale());
-                if (upperCaseString.codePointCount(0, upperCaseString.length()) == 1) {
-                    code = upperCaseString.codePointAt(0);
-                } else {
-                    // Some keys, such as [eszett], have upper case as multi-characters.
-                    onTextInput(upperCaseString);
+            } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+                if (revertSwapPunctuation(ic)) {
+                    // Likewise
                     return;
                 }
             }
+
+            // No cancelling of commit/double space/swap: we have a regular backspace.
+            // We should backspace one char and restart suggestion if at the end of a word.
+            if (mLastSelectionStart != mLastSelectionEnd) {
+                // If there is a selection, remove it.
+                final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
+                ic.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+                ic.deleteSurroundingText(lengthToDelete, 0);
+            } else {
+                // There is no selection, just delete one character.
+                if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
+                    // This should never happen.
+                    Log.e(TAG, "Backspace when we don't know the selection position");
+                }
+                ic.deleteSurroundingText(1, 0);
+                if (mDeleteCount > DELETE_ACCELERATE_AT) {
+                    ic.deleteSurroundingText(1, 0);
+                }
+            }
+            if (isSuggestionsRequested()) {
+                restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
+            }
         }
-        if (mHasUncommittedTypedChars) {
-            mComposingStringBuilder.append((char) code);
-            mWordComposer.add(code, keyCodes, x, y);
-            final InputConnection ic = getCurrentInputConnection();
+    }
+
+    // ic may be null
+    private boolean maybeStripSpaceWhileInBatchEdit(final InputConnection ic, final int code,
+            final int spaceState, final boolean isFromSuggestionStrip) {
+        if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+            removeTrailingSpaceWhileInBatchEdit(ic);
+            return false;
+        } else if ((SPACE_STATE_WEAK == spaceState
+                || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
+                && isFromSuggestionStrip) {
+            if (mSettingsValues.isWeakSpaceSwapper(code)) {
+                return true;
+            } else {
+                if (mSettingsValues.isWeakSpaceStripper(code)) {
+                    removeTrailingSpaceWhileInBatchEdit(ic);
+                }
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    private void handleCharacter(final int primaryCode, final int x,
+            final int y, final int spaceState) {
+        final InputConnection ic = getCurrentInputConnection();
+        if (null != ic) ic.beginBatchEdit();
+        // TODO: if ic is null, does it make any sense to call this?
+        handleCharacterWhileInBatchEdit(primaryCode, x, y, spaceState, ic);
+        if (null != ic) ic.endBatchEdit();
+    }
+
+    // "ic" may be null without this crashing, but the behavior will be really strange
+    private void handleCharacterWhileInBatchEdit(final int primaryCode,
+            final int x, final int y, final int spaceState, final InputConnection ic) {
+        boolean isComposingWord = mWordComposer.isComposingWord();
+
+        if (SPACE_STATE_PHANTOM == spaceState &&
+                !mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode)) {
+            if (isComposingWord) {
+                // Sanity check
+                throw new RuntimeException("Should not be composing here");
+            }
+            sendKeyCodePoint(Keyboard.CODE_SPACE);
+        }
+
+        if ((isAlphabet(primaryCode)
+                || mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode))
+                && isSuggestionsRequested() && !isCursorTouchingWord()) {
+            if (!isComposingWord) {
+                // Reset entirely the composing state anyway, then start composing a new word unless
+                // the character is a single quote. The idea here is, single quote is not a
+                // separator and it should be treated as a normal character, except in the first
+                // position where it should not start composing a word.
+                isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode);
+                // Here we don't need to reset the last composed word. It will be reset
+                // when we commit this one, if we ever do; if on the other hand we backspace
+                // it entirely and resume suggestions on the previous word, we'd like to still
+                // have touch coordinates for it.
+                resetComposingState(false /* alsoResetLastComposedWord */);
+                clearSuggestions();
+            }
+        }
+        if (isComposingWord) {
+            mWordComposer.add(
+                    primaryCode, x, y, mKeyboardSwitcher.getKeyboardView().getKeyDetector());
             if (ic != null) {
                 // If it's the first letter, make note of auto-caps state
                 if (mWordComposer.size() == 1) {
                     mWordComposer.setAutoCapitalized(getCurrentAutoCapsState());
-                    mComposingStateManager.onStartComposingText();
                 }
-                final CharSequence textWithUnderline =
-                        mComposingStateManager.isAutoCorrectionIndicatorOn()
-                                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                        this, mComposingStringBuilder)
-                                : mComposingStringBuilder;
-                ic.setComposingText(textWithUnderline, 1);
+                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
             }
             mHandler.postUpdateSuggestions();
         } else {
-            sendKeyChar((char)code);
-        }
-        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
-            swapSwapperAndSpace();
-        } else {
-            mJustAddedMagicSpace = false;
-        }
+            final boolean swapWeakSpace = maybeStripSpaceWhileInBatchEdit(ic, primaryCode,
+                    spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
 
-        switcher.updateShiftState();
-        if (LatinIME.PERF_DEBUG) measureCps();
-        TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
+            sendKeyCodePoint(primaryCode);
+
+            if (swapWeakSpace) {
+                swapSwapperAndSpaceWhileInBatchEdit(ic);
+                mSpaceState = SPACE_STATE_WEAK;
+            }
+            // Some characters are not word separators, yet they don't start a new
+            // composing span. For these, we haven't changed the suggestion strip, and
+            // if the "add to dictionary" hint is shown, we should do so now. Examples of
+            // such characters include single quote, dollar, and others; the exact list is
+            // the list of characters for which we enter handleCharacterWhileInBatchEdit
+            // that don't match the test if ((isAlphabet...)) at the top of this method.
+            if (null != mSuggestionsView && mSuggestionsView.dismissAddToDictionaryHint()) {
+                mHandler.postUpdateBigramPredictions();
+            }
+        }
+        Utils.Stats.onNonSeparator((char)primaryCode, x, y);
     }
 
-    private void handleSeparator(int primaryCode, int x, int y) {
-        mVoiceProxy.handleSeparator();
-        mComposingStateManager.onFinishComposingText();
-
+    // Returns true if we did an autocorrection, false otherwise.
+    private boolean handleSeparator(final int primaryCode, final int x, final int y,
+            final int spaceState) {
         // Should dismiss the "Touch again to save" message when handling separator
         if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
             mHandler.cancelUpdateBigramPredictions();
             mHandler.postUpdateSuggestions();
         }
 
-        boolean pickedDefault = false;
+        boolean didAutoCorrect = false;
         // Handle separator
         final InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
         }
-        if (mHasUncommittedTypedChars) {
+        if (mWordComposer.isComposingWord()) {
             // In certain languages where single quote is a separator, it's better
             // not to auto correct, but accept the typed word. For instance,
             // in Italian dov' should not be expanded to dove' because the elision
             // requires the last vowel to be removed.
             final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
-                    && !mInputTypeNoAutoCorrect;
+                    && !mInputAttributes.mInputTypeNoAutoCorrect;
             if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
-                pickedDefault = pickDefaultSuggestion(primaryCode);
+                commitCurrentAutoCorrection(primaryCode, ic);
+                didAutoCorrect = true;
             } else {
-                commitTyped(ic);
+                commitTyped(ic, primaryCode);
             }
         }
 
-        if (mJustAddedMagicSpace) {
-            if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
-                sendKeyChar((char)primaryCode);
-                swapSwapperAndSpace();
-            } else {
-                if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
-                sendKeyChar((char)primaryCode);
-                mJustAddedMagicSpace = false;
-            }
-        } else {
-            sendKeyChar((char)primaryCode);
-        }
+        final boolean swapWeakSpace = maybeStripSpaceWhileInBatchEdit(ic, primaryCode, spaceState,
+                KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
 
-        if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
-            maybeDoubleSpace();
+        if (SPACE_STATE_PHANTOM == spaceState &&
+                mSettingsValues.isPhantomSpacePromotingSymbol(primaryCode)) {
+            sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
+        sendKeyCodePoint(primaryCode);
 
-        TextEntryState.typedCharacter((char) primaryCode, true, x, y);
-
-        if (pickedDefault) {
-            CharSequence typedWord = mWordComposer.getTypedWord();
-            TextEntryState.backToAcceptedDefault(typedWord);
-            if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
-                InputConnectionCompatUtils.commitCorrection(
-                        ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
-            }
-        }
         if (Keyboard.CODE_SPACE == primaryCode) {
+            if (isSuggestionsRequested()) {
+                if (maybeDoubleSpaceWhileInBatchEdit(ic)) {
+                    mSpaceState = SPACE_STATE_DOUBLE;
+                } else if (!isShowingPunctuationList()) {
+                    mSpaceState = SPACE_STATE_WEAK;
+                }
+            }
+
+            mHandler.startDoubleSpacesTimer();
             if (!isCursorTouchingWord()) {
                 mHandler.cancelUpdateSuggestions();
                 mHandler.postUpdateBigramPredictions();
             }
         } else {
+            if (swapWeakSpace) {
+                swapSwapperAndSpaceWhileInBatchEdit(ic);
+                mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
+            } else if (SPACE_STATE_PHANTOM == spaceState) {
+                // If we are in phantom space state, and the user presses a separator, we want to
+                // stay in phantom space state so that the next keypress has a chance to add the
+                // space. For example, if I type "Good dat", pick "day" from the suggestion strip
+                // then insert a comma and go on to typing the next word, I want the space to be
+                // inserted automatically before the next word, the same way it is when I don't
+                // input the comma.
+                mSpaceState = SPACE_STATE_PHANTOM;
+            }
+
             // Set punctuation right away. onUpdateSelection will fire but tests whether it is
             // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
-        mKeyboardSwitcher.updateShiftState();
+
+        Utils.Stats.onSeparator((char)primaryCode, x, y);
+
         if (ic != null) {
             ic.endBatchEdit();
         }
+        return didAutoCorrect;
+    }
+
+    private CharSequence getTextWithUnderline(final CharSequence text) {
+        return mIsAutoCorrectionIndicatorOn
+                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
+                : text;
     }
 
     private void handleClose() {
-        commitTyped(getCurrentInputConnection());
-        mVoiceProxy.handleClose();
+        commitTyped(getCurrentInputConnection(), LastComposedWord.NOT_A_SEPARATOR);
         requestHideSelf(0);
         LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null)
@@ -1659,7 +1632,7 @@
     }
 
     public boolean isSuggestionsRequested() {
-        return mIsSettingsSuggestionStripOn
+        return mInputAttributes.mIsSettingsSuggestionStripOn
                 && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
     }
 
@@ -1677,11 +1650,11 @@
     public boolean isSuggestionsStripVisible() {
         if (mSuggestionsView == null)
             return false;
-        if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
+        if (mSuggestionsView.isShowingAddToDictionaryHint())
             return true;
         if (!isShowingSuggestionsStrip())
             return false;
-        if (mApplicationSpecifiedCompletionOn)
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn)
             return true;
         return isSuggestionsRequested();
     }
@@ -1705,55 +1678,48 @@
     }
 
     public void clearSuggestions() {
-        setSuggestions(SuggestedWords.EMPTY);
+        setSuggestions(SuggestedWords.EMPTY, false);
+        setAutoCorrectionIndicator(false);
     }
 
-    public void setSuggestions(SuggestedWords words) {
+    private void setSuggestions(final SuggestedWords words, final boolean isAutoCorrection) {
         if (mSuggestionsView != null) {
             mSuggestionsView.setSuggestions(words);
-            mKeyboardSwitcher.onAutoCorrectionStateChanged(
-                    words.hasWordAboveAutoCorrectionScoreThreshold());
+            mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection);
         }
+    }
 
+    private void setAutoCorrectionIndicator(final boolean newAutoCorrectionIndicator) {
         // Put a blue underline to a word in TextView which will be auto-corrected.
         final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            final boolean oldAutoCorrectionIndicator =
-                    mComposingStateManager.isAutoCorrectionIndicatorOn();
-            final boolean newAutoCorrectionIndicator = Utils.willAutoCorrect(words);
-            if (oldAutoCorrectionIndicator != newAutoCorrectionIndicator) {
-                if (LatinImeLogger.sDBG) {
-                    Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator
-                            + " -> " + newAutoCorrectionIndicator);
-                }
-                final CharSequence textWithUnderline = newAutoCorrectionIndicator
-                        ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                this, mComposingStringBuilder)
-                        : mComposingStringBuilder;
-                if (!TextUtils.isEmpty(textWithUnderline)) {
-                    ic.setComposingText(textWithUnderline, 1);
-                }
-                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
-            }
+        if (ic == null) return;
+        if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
+                && mWordComposer.isComposingWord()) {
+            mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
+            final CharSequence textWithUnderline =
+                    getTextWithUnderline(mWordComposer.getTypedWord());
+            ic.setComposingText(textWithUnderline, 1);
         }
     }
 
     public void updateSuggestions() {
         // Check if we have a suggestion engine attached.
-        if ((mSuggest == null || !isSuggestionsRequested())
-                && !mVoiceProxy.isVoiceInputHighlighted()) {
+        if ((mSuggest == null || !isSuggestionsRequested())) {
+            if (mWordComposer.isComposingWord()) {
+                Log.w(TAG, "Called updateSuggestions but suggestions were not requested!");
+                mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
+            }
             return;
         }
 
         mHandler.cancelUpdateSuggestions();
         mHandler.cancelUpdateBigramPredictions();
 
-        if (!mHasUncommittedTypedChars) {
+        if (!mWordComposer.isComposingWord()) {
             setPunctuationSuggestions();
             return;
         }
 
-        final WordComposer wordComposer = mWordComposer;
         // TODO: May need a better way of retrieving previous word
         final InputConnection ic = getCurrentInputConnection();
         final CharSequence prevWord;
@@ -1762,27 +1728,11 @@
         } else {
             prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
         }
-        // getSuggestedWordBuilder handles gracefully a null value of prevWord
-        final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
-                wordComposer, prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
 
-        boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
-        final CharSequence typedWord = wordComposer.getTypedWord();
-        // Here, we want to promote a whitelisted word if exists.
-        // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
-        // but still autocorrected from - in the case the whitelist only capitalizes the word.
-        // The whitelist should be case-insensitive, so it's not possible to be consistent with
-        // a boolean flag. Right now this is handled with a slight hack in
-        // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
-        final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
-                mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization());
-        if (mCorrectionMode == Suggest.CORRECTION_FULL
-                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
-            autoCorrectionAvailable |= (!allowsToBeAutoCorrected);
-        }
-        // Don't auto-correct words with multiple capital letter
-        autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
-        autoCorrectionAvailable &= !TextEntryState.isRecorrecting();
+        final CharSequence typedWord = mWordComposer.getTypedWord();
+        // getSuggestedWords handles gracefully a null value of prevWord
+        final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
+                prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode);
 
         // Basically, we update the suggestion strip only when suggestion count > 1.  However,
         // there is an exception: We update the suggestion strip whenever typed word's length
@@ -1790,145 +1740,128 @@
         // in most cases, suggestion count is 1 when typed word's length is 1, but we do always
         // need to clear the previous state when the user starts typing a word (i.e. typed word's
         // length == 1).
-        if (typedWord != null) {
-            if (builder.size() > 1 || typedWord.length() == 1 || (!allowsToBeAutoCorrected)
-                    || mSuggestionsView.isShowingAddToDictionaryHint()) {
-                builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion(
-                        autoCorrectionAvailable);
-            } else {
-                SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions();
-                if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
-                    if (builder.size() == 0) {
-                        return;
-                    }
-                    previousSuggestions = SuggestedWords.EMPTY;
-                }
-                builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+        if (suggestedWords.size() > 1 || typedWord.length() == 1
+                || !suggestedWords.mAllowsToBeAutoCorrected
+                || mSuggestionsView.isShowingAddToDictionaryHint()) {
+            showSuggestions(suggestedWords, typedWord);
+        } else {
+            SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions();
+            if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
+                previousSuggestions = SuggestedWords.EMPTY;
             }
+            final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+                    SuggestedWords.getTypedWordAndPreviousSuggestions(
+                            typedWord, previousSuggestions);
+            final SuggestedWords obsoleteSuggestedWords =
+                    new SuggestedWords(typedWordAndPreviousSuggestions,
+                            false /* typedWordValid */,
+                            false /* hasAutoCorrectionCandidate */,
+                            false /* allowsToBeAutoCorrected */,
+                            false /* isPunctuationSuggestions */,
+                            true /* isObsoleteSuggestions */);
+            showSuggestions(obsoleteSuggestedWords, typedWord);
         }
-        showSuggestions(builder.build(), typedWord);
     }
 
-    public void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
-        final boolean shouldBlockAutoCorrectionBySafetyNet =
-                Utils.shouldBlockAutoCorrectionBySafetyNet(suggestedWords, mSuggest);
-        if (shouldBlockAutoCorrectionBySafetyNet) {
-            suggestedWords.setShouldBlockAutoCorrection();
-        }
-        setSuggestions(suggestedWords);
+    public void showSuggestions(final SuggestedWords suggestedWords, final CharSequence typedWord) {
+        final CharSequence autoCorrection;
         if (suggestedWords.size() > 0) {
-            if (shouldBlockAutoCorrectionBySafetyNet) {
-                mBestWord = typedWord;
-            } else if (suggestedWords.hasAutoCorrectionWord()) {
-                mBestWord = suggestedWords.getWord(1);
+            if (suggestedWords.hasAutoCorrectionWord()) {
+                autoCorrection = suggestedWords.getWord(1);
             } else {
-                mBestWord = typedWord;
+                autoCorrection = typedWord;
             }
         } else {
-            mBestWord = null;
+            autoCorrection = null;
         }
+        mWordComposer.setAutoCorrection(autoCorrection);
+        final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
+        setSuggestions(suggestedWords, isAutoCorrection);
+        setAutoCorrectionIndicator(isAutoCorrection);
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
 
-    private boolean pickDefaultSuggestion(int separatorCode) {
+    private void commitCurrentAutoCorrection(final int separatorCodePoint,
+            final InputConnection ic) {
         // Complete any pending suggestions query first
         if (mHandler.hasPendingUpdateSuggestions()) {
             mHandler.cancelUpdateSuggestions();
             updateSuggestions();
         }
-        if (mBestWord != null && mBestWord.length() > 0) {
-            TextEntryState.acceptedDefault(mWordComposer.getTypedWord(), mBestWord, separatorCode);
+        final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull();
+        if (autoCorrection != null) {
+            final String typedWord = mWordComposer.getTypedWord();
+            if (TextUtils.isEmpty(typedWord)) {
+                throw new RuntimeException("We have an auto-correction but the typed word "
+                        + "is empty? Impossible! I must commit suicide.");
+            }
+            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
             mExpectingUpdateSelection = true;
-            commitBestWord(mBestWord);
-            // Add the word to the user unigram dictionary if it's not a known word
-            addToUserUnigramAndBigramDictionaries(mBestWord,
-                    UserUnigramDictionary.FREQUENCY_FOR_TYPED);
-            return true;
+            commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
+                    separatorCodePoint);
+            // Add the word to the user history dictionary
+            addToUserHistoryDictionary(autoCorrection);
+            if (!typedWord.equals(autoCorrection) && null != ic) {
+                // This will make the correction flash for a short while as a visual clue
+                // to the user that auto-correction happened.
+                ic.commitCorrection(new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
+                        typedWord, autoCorrection));
+            }
         }
-        return false;
     }
 
     @Override
-    public void pickSuggestionManually(int index, CharSequence suggestion) {
-        mComposingStateManager.onFinishComposingText();
-        SuggestedWords suggestions = mSuggestionsView.getSuggestions();
-        mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
-                mSettingsValues.mWordSeparators);
+    public void pickSuggestionManually(final int index, final CharSequence suggestion) {
+        final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
 
-        final boolean recorrecting = TextEntryState.isRecorrecting();
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            ic.beginBatchEdit();
-        }
-        if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
-                && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
-            if (ic != null) {
-                final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
-                ic.commitCompletion(completionInfo);
+        if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0) {
+            int firstChar = Character.codePointAt(suggestion, 0);
+            if ((!mSettingsValues.isWeakSpaceStripper(firstChar))
+                    && (!mSettingsValues.isWeakSpaceSwapper(firstChar))) {
+                sendKeyCodePoint(Keyboard.CODE_SPACE);
             }
-            mCommittedLength = suggestion.length();
+        }
+
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn
+                && mApplicationSpecifiedCompletions != null
+                && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
             if (mSuggestionsView != null) {
                 mSuggestionsView.clear();
             }
             mKeyboardSwitcher.updateShiftState();
+            resetComposingState(true /* alsoResetLastComposedWord */);
+            final InputConnection ic = getCurrentInputConnection();
             if (ic != null) {
-                ic.endBatchEdit();
+                final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
+                ic.commitCompletion(completionInfo);
             }
             return;
         }
 
-        // If this is a punctuation, apply it through the normal key press
-        if (suggestion.length() == 1 && (mSettingsValues.isWordSeparator(suggestion.charAt(0))
-                || mSettingsValues.isSuggestedPunctuation(suggestion.charAt(0)))) {
+        // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
+        if (suggestion.length() == 1 && isShowingPunctuationList()) {
             // Word separators are suggested before the user inputs something.
             // So, LatinImeLogger logs "" as a user's input.
-            LatinImeLogger.logOnManualSuggestion(
-                    "", suggestion.toString(), index, suggestions.mWords);
-            // Find out whether the previous character is a space. If it is, as a special case
-            // for punctuation entered through the suggestion strip, it should be considered
-            // a magic space even if it was a normal space. This is meant to help in case the user
-            // pressed space on purpose of displaying the suggestion strip punctuation.
-            final int rawPrimaryCode = suggestion.charAt(0);
-            // Maybe apply the "bidi mirrored" conversions for parentheses
-            final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
-            final boolean isRtl = keyboard != null && keyboard.mIsRtlKeyboard;
-            final int primaryCode = Key.getRtlParenthesisCode(rawPrimaryCode, isRtl);
-
-            final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
-            final int toLeft = (ic == null || TextUtils.isEmpty(beforeText))
-                    ? 0 : beforeText.charAt(0);
-            final boolean oldMagicSpace = mJustAddedMagicSpace;
-            if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true;
-            onCodeInput(primaryCode, new int[] { primaryCode },
-                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
-                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
-            mJustAddedMagicSpace = oldMagicSpace;
-            if (ic != null) {
-                ic.endBatchEdit();
-            }
+            LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
+            // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
+            final int primaryCode = suggestion.charAt(0);
+            onCodeInput(primaryCode,
+                    KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
+                    KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
             return;
         }
-        if (!mHasUncommittedTypedChars) {
-            // If we are not composing a word, then it was a suggestion inferred from
-            // context - no user input. We should reset the word composer.
-            mWordComposer.reset();
-        }
+        // We need to log before we commit, because the word composer will store away the user
+        // typed word.
+        LatinImeLogger.logOnManualSuggestion(mWordComposer.getTypedWord().toString(),
+                suggestion.toString(), index, suggestedWords);
         mExpectingUpdateSelection = true;
-        commitBestWord(suggestion);
-        // Add the word to the auto dictionary if it's not a known word
-        if (index == 0) {
-            addToUserUnigramAndBigramDictionaries(suggestion,
-                    UserUnigramDictionary.FREQUENCY_FOR_PICKED);
-        } else {
-            addToOnlyBigramDictionary(suggestion, 1);
-        }
-        LatinImeLogger.logOnManualSuggestion(mComposingStringBuilder.toString(),
-                suggestion.toString(), index, suggestions.mWords);
-        TextEntryState.acceptedSuggestion(mComposingStringBuilder.toString(), suggestion);
-        // Follow it with a space
-        if (mInsertSpaceOnPickSuggestionManually && !recorrecting) {
-            sendMagicSpace();
-        }
+        commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
+                LastComposedWord.NOT_A_SEPARATOR);
+        // Add the word to the user history dictionary
+        addToUserHistoryDictionary(suggestion);
+        mSpaceState = SPACE_STATE_PHANTOM;
+        // TODO: is this necessary?
+        mKeyboardSwitcher.updateShiftState();
 
         // We should show the "Touch again to save" hint if the user pressed the first entry
         // AND either:
@@ -1946,13 +1879,8 @@
                         || !AutoCorrection.isValidWord(
                                 mSuggest.getUnigramDictionaries(), suggestion, true));
 
-        if (!recorrecting) {
-            // Fool the state watcher so that a subsequent backspace will not do a revert, unless
-            // we just did a correction, in which case we need to stay in
-            // TextEntryState.State.PICKED_SUGGESTION state.
-            TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
-                    WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-        }
+        Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
+                WordComposer.NOT_A_COORDINATE);
         if (!showingAddToDictionaryHint) {
             // If we're not showing the "Touch again to save", then show corrections again.
             // In case the cursor position doesn't change, make sure we show the suggestions again.
@@ -1960,29 +1888,23 @@
             // Updating the predictions right away may be slow and feel unresponsive on slower
             // terminals. On the other hand if we just postUpdateBigramPredictions() it will
             // take a noticeable delay to update them which may feel uneasy.
-        }
-        if (showingAddToDictionaryHint) {
-            if (mIsUserDictionaryAvaliable) {
-                mSuggestionsView.showAddToDictionaryHint(suggestion);
+        } else {
+            if (mIsUserDictionaryAvailable) {
+                mSuggestionsView.showAddToDictionaryHint(
+                        suggestion, mSettingsValues.mHintToSaveText);
             } else {
                 mHandler.postUpdateSuggestions();
             }
         }
-        if (ic != null) {
-            ic.endBatchEdit();
-        }
     }
 
     /**
      * Commits the chosen word to the text field and saves it for later retrieval.
      */
-    private void commitBestWord(CharSequence bestWord) {
-        final KeyboardSwitcher switcher = mKeyboardSwitcher;
-        if (!switcher.isKeyboardAvailable())
-            return;
+    private void commitChosenWord(final CharSequence bestWord, final int commitType,
+            final int separatorCode) {
         final InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
-            mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators);
             if (mSettingsValues.mEnableSuggestionSpanInsertion) {
                 final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
                 ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
@@ -1991,11 +1913,14 @@
                 ic.commitText(bestWord, 1);
             }
         }
-        mHasUncommittedTypedChars = false;
-        mCommittedLength = bestWord.length();
+        // TODO: figure out here if this is an auto-correct or if the best word is actually
+        // what user typed. Note: currently this is done much later in
+        // LastComposedWord#didCommitTypedWord by string equality of the remembered
+        // strings.
+        mLastComposedWord = mWordComposer.commitWord(commitType, bestWord.toString(),
+                separatorCode);
     }
 
-    private static final WordComposer sEmptyWordComposer = new WordComposer();
     public void updateBigramPredictions() {
         if (mSuggest == null || !isSuggestionsRequested())
             return;
@@ -2005,41 +1930,36 @@
             return;
         }
 
-        final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
-                mSettingsValues.mWordSeparators);
-        SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(sEmptyWordComposer,
-                prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
+        final SuggestedWords suggestedWords;
+        if (mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
+            final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
+                    mSettingsValues.mWordSeparators);
+            if (!TextUtils.isEmpty(prevWord)) {
+                suggestedWords = mSuggest.getBigramPredictions(prevWord);
+            } else {
+                suggestedWords = null;
+            }
+        } else {
+            suggestedWords = null;
+        }
 
-        if (builder.size() > 0) {
+        if (null != suggestedWords && suggestedWords.size() > 0) {
             // Explicitly supply an empty typed word (the no-second-arg version of
             // showSuggestions will retrieve the word near the cursor, we don't want that here)
-            showSuggestions(builder.build(), "");
+            showSuggestions(suggestedWords, "");
         } else {
             if (!isShowingPunctuationList()) setPunctuationSuggestions();
         }
     }
 
     public void setPunctuationSuggestions() {
-        setSuggestions(mSettingsValues.mSuggestPuncList);
+        setSuggestions(mSettingsValues.mSuggestPuncList, false);
+        setAutoCorrectionIndicator(false);
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
 
-    private void addToUserUnigramAndBigramDictionaries(CharSequence suggestion,
-            int frequencyDelta) {
-        checkAddToDictionary(suggestion, frequencyDelta, false);
-    }
-
-    private void addToOnlyBigramDictionary(CharSequence suggestion, int frequencyDelta) {
-        checkAddToDictionary(suggestion, frequencyDelta, true);
-    }
-
-    /**
-     * Adds to the UserBigramDictionary and/or UserUnigramDictionary
-     * @param selectedANotTypedWord true if it should be added to bigram dictionary if possible
-     */
-    private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
-            boolean selectedANotTypedWord) {
-        if (suggestion == null || suggestion.length() < 1) return;
+    private void addToUserHistoryDictionary(final CharSequence suggestion) {
+        if (TextUtils.isEmpty(suggestion)) 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
@@ -2049,102 +1969,179 @@
             return;
         }
 
-        if (null != mSuggest && null != mUserUnigramDictionary) {
-            final boolean selectedATypedWordAndItsInUserUnigramDic =
-                    !selectedANotTypedWord && mUserUnigramDictionary.isValidWord(suggestion);
-            final boolean isValidWord = AutoCorrection.isValidWord(
-                    mSuggest.getUnigramDictionaries(), suggestion, true);
-            final boolean needsToAddToUserUnigramDictionary =
-                    selectedATypedWordAndItsInUserUnigramDic || !isValidWord;
-            if (needsToAddToUserUnigramDictionary) {
-                mUserUnigramDictionary.addWord(suggestion.toString(), frequencyDelta);
-            }
-        }
-
-        if (mUserBigramDictionary != null) {
-            // We don't want to register as bigrams words separated by a separator.
-            // For example "I will, and you too" : we don't want the pair ("will" "and") to be
-            // a bigram.
+        if (mUserHistoryDictionary != null) {
             final InputConnection ic = getCurrentInputConnection();
+            final CharSequence prevWord;
             if (null != ic) {
-                final CharSequence prevWord =
-                        EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
-                if (!TextUtils.isEmpty(prevWord)) {
-                    mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
-                }
+                prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
+            } else {
+                prevWord = null;
             }
+            final String secondWord;
+            if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
+                secondWord = suggestion.toString().toLowerCase(mSubtypeSwitcher.getInputLocale());
+            } else {
+                secondWord = suggestion.toString();
+            }
+            mUserHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
+                    secondWord);
         }
     }
 
     public boolean isCursorTouchingWord() {
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return false;
-        CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
-        CharSequence toRight = ic.getTextAfterCursor(1, 0);
-        if (!TextUtils.isEmpty(toLeft)
-                && !mSettingsValues.isWordSeparator(toLeft.charAt(0))
-                && !mSettingsValues.isSuggestedPunctuation(toLeft.charAt(0))) {
+        CharSequence before = ic.getTextBeforeCursor(1, 0);
+        CharSequence after = ic.getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(before) && !mSettingsValues.isWordSeparator(before.charAt(0))
+                && !mSettingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) {
             return true;
         }
-        if (!TextUtils.isEmpty(toRight)
-                && !mSettingsValues.isWordSeparator(toRight.charAt(0))
-                && !mSettingsValues.isSuggestedPunctuation(toRight.charAt(0))) {
+        if (!TextUtils.isEmpty(after) && !mSettingsValues.isWordSeparator(after.charAt(0))
+                && !mSettingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) {
             return true;
         }
         return false;
     }
 
-    // "ic" must not null
-    private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
-        CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
+    // "ic" must not be null
+    private static boolean sameAsTextBeforeCursor(final InputConnection ic,
+            final CharSequence text) {
+        final CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
         return TextUtils.equals(text, beforeText);
     }
 
-    // "ic" must not null
-    private void revertLastWord(final InputConnection ic) {
-        if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
-            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+    // "ic" must not be null
+    /**
+     * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
+     * word, else do nothing.
+     */
+    private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
+            final InputConnection ic) {
+        // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+        // non-whitespace, non-separator, non-start-of-text)
+        // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(1, 0);
+        if (TextUtils.isEmpty(textBeforeCursor)
+                || mSettingsValues.isWordSeparator(textBeforeCursor.charAt(0))) return;
+
+        // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
+        // separator or end of line/text)
+        // Example: "test|"<EOL> "te|st" get rejected here
+        final CharSequence textAfterCursor = ic.getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(textAfterCursor)
+                && !mSettingsValues.isWordSeparator(textAfterCursor.charAt(0))) return;
+
+        // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
+        // Example: " -|" gets rejected here but "e-|" and "e|" are okay
+        CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+        // We don't suggest on leading single quotes, so we have to remove them from the word if
+        // it starts with single quotes.
+        while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) {
+            word = word.subSequence(1, word.length());
+        }
+        if (TextUtils.isEmpty(word)) return;
+        final char firstChar = word.charAt(0); // we just tested that word is not empty
+        if (word.length() == 1 && !Character.isLetter(firstChar)) return;
+
+        // We only suggest on words that start with a letter or a symbol that is excluded from
+        // word separators (see #handleCharacterWhileInBatchEdit).
+        if (!(isAlphabet(firstChar)
+                || mSettingsValues.isSymbolExcludedFromWordSeparators(firstChar))) {
             return;
         }
 
-        final CharSequence separator = ic.getTextBeforeCursor(1, 0);
-        ic.deleteSurroundingText(1, 0);
-        final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
-        ic.deleteSurroundingText(mCommittedLength, 0);
+        // Okay, we are at the end of a word. Restart suggestions.
+        restartSuggestionsOnWordBeforeCursor(ic, word);
+    }
 
-        // Re-insert "separator" only when the deleted character was word separator and the
-        // composing text wasn't equal to the auto-corrected text which can be found before
-        // the cursor.
-        if (!TextUtils.isEmpty(separator)
-                && mSettingsValues.isWordSeparator(separator.charAt(0))
-                && !TextUtils.equals(mComposingStringBuilder, textToTheLeft)) {
-            ic.commitText(mComposingStringBuilder, 1);
-            TextEntryState.acceptedTyped(mComposingStringBuilder);
-            ic.commitText(separator, 1);
-            TextEntryState.typedCharacter(separator.charAt(0), true,
-                    WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-            // Clear composing text
-            mComposingStringBuilder.setLength(0);
-        } else {
-            mHasUncommittedTypedChars = true;
-            ic.setComposingText(mComposingStringBuilder, 1);
-            TextEntryState.backspace();
+    // "ic" must not be null
+    private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
+            final CharSequence word) {
+        mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
+        ic.deleteSurroundingText(word.length(), 0);
+        ic.setComposingText(word, 1);
+        mHandler.postUpdateSuggestions();
+    }
+
+    // "ic" must not be null
+    private void revertCommit(final InputConnection ic) {
+        final String originallyTypedWord = mLastComposedWord.mTypedWord;
+        final CharSequence committedWord = mLastComposedWord.mCommittedWord;
+        final int cancelLength = committedWord.length();
+        final int separatorLength = LastComposedWord.getSeparatorLength(
+                mLastComposedWord.mSeparatorCode);
+        // TODO: should we check our saved separator against the actual contents of the text view?
+        if (DEBUG) {
+            if (mWordComposer.isComposingWord()) {
+                throw new RuntimeException("revertCommit, but we are composing a word");
+            }
+            final String wordBeforeCursor =
+                    ic.getTextBeforeCursor(cancelLength + separatorLength, 0)
+                            .subSequence(0, cancelLength).toString();
+            if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
+                throw new RuntimeException("revertCommit check failed: we thought we were "
+                        + "reverting \"" + committedWord
+                        + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
+            }
         }
+        ic.deleteSurroundingText(cancelLength + separatorLength, 0);
+        if (0 == separatorLength || mLastComposedWord.didCommitTypedWord()) {
+            // This is the case when we cancel a manual pick.
+            // We should restart suggestion on the word right away.
+            mWordComposer.resumeSuggestionOnLastComposedWord(mLastComposedWord);
+            ic.setComposingText(originallyTypedWord, 1);
+        } else {
+            ic.commitText(originallyTypedWord, 1);
+            // Re-insert the separator
+            sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
+            Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
+                    WordComposer.NOT_A_COORDINATE);
+            // Don't restart suggestion yet. We'll restart if the user deletes the
+            // separator.
+        }
+        mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
         mHandler.cancelUpdateBigramPredictions();
         mHandler.postUpdateSuggestions();
     }
 
-    // "ic" must not null
-    private boolean revertDoubleSpace(final InputConnection ic) {
+    // "ic" must not be null
+    private boolean revertDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
         mHandler.cancelDoubleSpacesTimer();
         // Here we test whether we indeed have a period and a space before us. This should not
         // be needed, but it's there just in case something went wrong.
         final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
-        if (!". ".equals(textBeforeCursor))
+        if (!". ".equals(textBeforeCursor)) {
+            // Theoretically we should not be coming here if there isn't ". " before the
+            // cursor, but the application may be changing the text while we are typing, so
+            // anything goes. We should not crash.
+            Log.d(TAG, "Tried to revert double-space combo but we didn't find "
+                    + "\". \" just before the cursor.");
             return false;
-        ic.beginBatchEdit();
+        }
         ic.deleteSurroundingText(2, 0);
         ic.commitText("  ", 1);
+        return true;
+    }
+
+    private static boolean revertSwapPunctuation(final InputConnection ic) {
+        // Here we test whether we indeed have a space and something else before us. This should not
+        // be needed, but it's there just in case something went wrong.
+        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
+        // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
+        // enter surrogate pairs this code will have been removed.
+        if (TextUtils.isEmpty(textBeforeCursor)
+                || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) {
+            // We may only come here if the application is changing the text while we are typing.
+            // This is quite a broken case, but not logically impossible, so we shouldn't crash,
+            // but some debugging log may be in order.
+            Log.d(TAG, "Tried to revert a swap of punctuation but we didn't "
+                    + "find a space just before the cursor.");
+            return false;
+        }
+        ic.beginBatchEdit();
+        ic.deleteSurroundingText(2, 0);
+        ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
         ic.endBatchEdit();
         return true;
     }
@@ -2153,12 +2150,6 @@
         return mSettingsValues.isWordSeparator(code);
     }
 
-    private void sendMagicSpace() {
-        sendKeyChar((char)Keyboard.CODE_SPACE);
-        mJustAddedMagicSpace = true;
-        mKeyboardSwitcher.updateShiftState();
-    }
-
     public boolean preferCapitalization() {
         return mWordComposer.isFirstCharCapitalized();
     }
@@ -2166,11 +2157,6 @@
     // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
     // according to new language or mode.
     public void onRefreshKeyboard() {
-        if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
-            // Before Honeycomb, Voice IME is in LatinIME and it changes the current input view,
-            // so that we need to re-create the keyboard input view here.
-            setInputView(mKeyboardSwitcher.onCreateInputView());
-        }
         // When the device locale is changed in SetupWizard etc., this method may get called via
         // onConfigurationChanged before SoftInputWindow is shown.
         if (mKeyboardSwitcher.getKeyboardView() != null) {
@@ -2178,142 +2164,68 @@
             mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues);
         }
         initSuggest();
+        updateCorrectionMode();
         loadSettings();
-    }
-
-    @Override
-    public void onPress(int primaryCode, boolean withSliding) {
-        final KeyboardSwitcher switcher = mKeyboardSwitcher;
-        if (switcher.isVibrateAndSoundFeedbackRequired()) {
-            vibrate();
-            playKeyClick(primaryCode);
-        }
-        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
-        if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
-            switcher.onPressShift(withSliding);
-        } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
-            switcher.onPressSymbol();
+        // Since we just changed languages, we should re-evaluate suggestions with whatever word
+        // we are currently composing. If we are not composing anything, we may want to display
+        // predictions or punctuation signs (which is done by updateBigramPredictions anyway).
+        if (isCursorTouchingWord()) {
+            mHandler.postUpdateSuggestions();
         } else {
-            switcher.onOtherKeyPressed();
+            mHandler.postUpdateBigramPredictions();
         }
     }
 
+    public void hapticAndAudioFeedback(final int primaryCode) {
+        mFeedbackManager.hapticAndAudioFeedback(primaryCode, mKeyboardSwitcher.getKeyboardView());
+    }
+
     @Override
-    public void onRelease(int primaryCode, boolean withSliding) {
-        KeyboardSwitcher switcher = mKeyboardSwitcher;
-        // Reset any drag flags in the keyboard
-        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
-        if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
-            switcher.onReleaseShift(withSliding);
-        } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
-            switcher.onReleaseSymbol();
-        }
+    public void onPressKey(int primaryCode) {
+        mKeyboardSwitcher.onPressKey(primaryCode);
     }
 
+    @Override
+    public void onReleaseKey(int primaryCode, boolean withSliding) {
+        mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
+
+        // If accessibility is on, ensure the user receives keyboard state updates.
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            switch (primaryCode) {
+            case Keyboard.CODE_SHIFT:
+                AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
+                break;
+            case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
+                AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
+                break;
+            }
+        }
+    }
 
     // receive ringer mode change and network state change.
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
-                updateRingerMode();
-            } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                 mSubtypeSwitcher.onNetworkStateChanged(intent);
+            } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+                mFeedbackManager.onRingerModeChanged();
             }
         }
     };
 
-    // update keypress sound volume
-    private void updateSoundEffectVolume() {
-        mFxVolume = Utils.getCurrentKeypressSoundVolume(mPrefs, mResources);
-    }
-
-    // update flags for silent mode
-    private void updateRingerMode() {
-        if (mAudioManager == null) {
-            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-            if (mAudioManager == null) return;
-        }
-        mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
-    }
-
-    private void updateKeypressVibrationDuration() {
-        mKeypressVibrationDuration = Utils.getCurrentVibrationDuration(mPrefs, mResources);
-    }
-
-    private void playKeyClick(int primaryCode) {
-        // if mAudioManager is null, we don't have the ringer state yet
-        // mAudioManager will be set by updateRingerMode
-        if (mAudioManager == null) {
-            if (mKeyboardSwitcher.getKeyboardView() != null) {
-                updateRingerMode();
-            }
-        }
-        if (isSoundOn()) {
-            final int sound;
-            switch (primaryCode) {
-            case Keyboard.CODE_DELETE:
-                sound = AudioManager.FX_KEYPRESS_DELETE;
-                break;
-            case Keyboard.CODE_ENTER:
-                sound = AudioManager.FX_KEYPRESS_RETURN;
-                break;
-            case Keyboard.CODE_SPACE:
-                sound = AudioManager.FX_KEYPRESS_SPACEBAR;
-                break;
-            default:
-                sound = AudioManager.FX_KEYPRESS_STANDARD;
-                break;
-            }
-            mAudioManager.playSoundEffect(sound, mFxVolume);
-        }
-    }
-
-    public void vibrate() {
-        if (!mSettingsValues.mVibrateOn) {
-            return;
-        }
-        if (mKeypressVibrationDuration < 0) {
-            // Go ahead with the system default
-            LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
-            if (inputView != null) {
-                inputView.performHapticFeedback(
-                        HapticFeedbackConstants.KEYBOARD_TAP,
-                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
-            }
-        } else if (mVibrator != null) {
-            mVibrator.vibrate(mKeypressVibrationDuration);
-        }
-    }
-
-    public WordComposer getCurrentWord() {
-        return mWordComposer;
-    }
-
-    boolean isSoundOn() {
-        return mSettingsValues.mSoundOn && !mSilentModeOn;
-    }
-
     private void updateCorrectionMode() {
         // TODO: cleanup messy flags
         final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
-                && !mInputTypeNoAutoCorrect;
-        mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled)
-                ? Suggest.CORRECTION_FULL
-                : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
-        mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect
-                && mSettingsValues.mAutoCorrectEnabled)
+                && !mInputAttributes.mInputTypeNoAutoCorrect;
+        mCorrectionMode = shouldAutoCorrect ? Suggest.CORRECTION_FULL : Suggest.CORRECTION_NONE;
+        mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect)
                 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
-        if (mSuggest != null) {
-            mSuggest.setCorrectionMode(mCorrectionMode);
-        }
     }
 
-    private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) {
-        final String suggestionVisiblityStr = prefs.getString(
-                Settings.PREF_SHOW_SUGGESTIONS_SETTING,
-                res.getString(R.string.prefs_suggestion_visibility_default_value));
+    private void updateSuggestionVisibility(final Resources res) {
+        final String suggestionVisiblityStr = mSettingsValues.mShowSuggestionsSetting;
         for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
             if (suggestionVisiblityStr.equals(res.getString(visibility))) {
                 mSuggestionVisibility = visibility;
@@ -2352,7 +2264,8 @@
                 switch (position) {
                 case 0:
                     Intent intent = CompatUtils.getInputLanguageSelectionIntent(
-                            mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
+                            SubtypeUtils.getInputMethodId(getPackageName()),
+                            Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                     startActivity(intent);
@@ -2369,30 +2282,23 @@
         showOptionDialogInternal(builder.create());
     }
 
-    private void showOptionsMenu() {
-        final CharSequence title = getString(R.string.english_ime_input_options);
-        final CharSequence[] items = new CharSequence[] {
-                getString(R.string.selectInputMethod),
-                getString(R.string.english_ime_settings),
-        };
-        final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface di, int position) {
-                di.dismiss();
-                switch (position) {
-                case 0:
-                    mImm.showInputMethodPicker();
-                    break;
-                case 1:
-                    launchSettings();
-                    break;
-                }
-            }
-        };
-        final AlertDialog.Builder builder = new AlertDialog.Builder(this)
-                .setItems(items, listener)
-                .setTitle(title);
-        showOptionDialogInternal(builder.create());
+    private void showOptionDialogInternal(AlertDialog dialog) {
+        final IBinder windowToken = KeyboardSwitcher.getInstance().getKeyboardView()
+                .getWindowToken();
+        if (windowToken == null) return;
+
+        dialog.setCancelable(true);
+        dialog.setCanceledOnTouchOutside(true);
+
+        final Window window = dialog.getWindow();
+        final WindowManager.LayoutParams lp = window.getAttributes();
+        lp.token = windowToken;
+        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        window.setAttributes(lp);
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+
+        mOptionsDialog = dialog;
+        dialog.show();
     }
 
     @Override
@@ -2401,35 +2307,16 @@
 
         final Printer p = new PrintWriterPrinter(fout);
         p.println("LatinIME state :");
-        p.println("  Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
-        p.println("  mComposingStringBuilder=" + mComposingStringBuilder.toString());
-        p.println("  mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
+        final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+        final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
+        p.println("  Keyboard mode = " + keyboardMode);
+        p.println("  mIsSuggestionsRequested=" + mInputAttributes.mIsSettingsSuggestionStripOn);
         p.println("  mCorrectionMode=" + mCorrectionMode);
-        p.println("  mHasUncommittedTypedChars=" + mHasUncommittedTypedChars);
+        p.println("  isComposingWord=" + mWordComposer.isComposingWord());
         p.println("  mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled);
-        p.println("  mInsertSpaceOnPickSuggestionManually=" + mInsertSpaceOnPickSuggestionManually);
-        p.println("  mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
-        p.println("  TextEntryState.state=" + TextEntryState.getState());
         p.println("  mSoundOn=" + mSettingsValues.mSoundOn);
         p.println("  mVibrateOn=" + mSettingsValues.mVibrateOn);
         p.println("  mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn);
-    }
-
-    // Characters per second measurement
-
-    private long mLastCpsTime;
-    private static final int CPS_BUFFER_SIZE = 16;
-    private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
-    private int mCpsIndex;
-
-    private void measureCps() {
-        long now = System.currentTimeMillis();
-        if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
-        mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
-        mLastCpsTime = now;
-        mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
-        long total = 0;
-        for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
-        System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
+        p.println("  mInputAttributes=" + mInputAttributes.toString());
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index ae8eb37..dc0868e 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,24 +16,22 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.Dictionary.DataType;
-
-import android.content.Context;
 import android.content.SharedPreferences;
+import android.view.inputmethod.EditorInfo;
 
-import java.util.List;
+import com.android.inputmethod.keyboard.Keyboard;
 
 public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
 
     public static boolean sDBG = false;
     public static boolean sVISUALDEBUG = false;
+    public static boolean sUsabilityStudy = false;
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
     }
 
-    public static void init(Context context, SharedPreferences prefs) {
+    public static void init(LatinIME context, SharedPreferences prefs) {
     }
 
     public static void commit() {
@@ -43,8 +41,8 @@
     }
 
     public static void logOnManualSuggestion(
-            String before, String after, int position, List<CharSequence> suggestions) {
-   }
+            String before, String after, int position, SuggestedWords suggestedWords) {
+    }
 
     public static void logOnAutoCorrection(String before, String after, int separatorCode) {
     }
@@ -52,7 +50,7 @@
     public static void logOnAutoCorrectionCancelled() {
     }
 
-    public static void logOnDelete() {
+    public static void logOnDelete(int x, int y) {
     }
 
     public static void logOnInputChar() {
@@ -67,10 +65,13 @@
     public static void logOnWarning(String warning) {
     }
 
+    public static void onStartInputView(EditorInfo editorInfo) {
+    }
+
     public static void onStartSuggestion(CharSequence previousWords) {
     }
 
-    public static void onAddSuggestedWord(String word, int typeId, DataType dataType) {
+    public static void onAddSuggestedWord(String word, int typeId, int dataType) {
     }
 
     public static void onSetKeyboard(Keyboard kb) {
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index e05b47c..f19c59a 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -161,19 +161,36 @@
         return LOCALE_MATCH <= level;
     }
 
-    /**
-     * Sets the system locale for this process.
-     *
-     * @param res the resources to use. Pass current resources.
-     * @param newLocale the locale to change to.
-     * @return the old locale.
-     */
-    public static Locale setSystemLocale(final Resources res, final Locale newLocale) {
-        final Configuration conf = res.getConfiguration();
-        final Locale saveLocale = conf.locale;
-        conf.locale = newLocale;
-        res.updateConfiguration(conf, res.getDisplayMetrics());
-        return saveLocale;
+    static final Object sLockForRunInLocale = new Object();
+
+    public abstract static class RunInLocale<T> {
+        protected abstract T job(Resources res);
+
+        /**
+         * Execute {@link #job(Resources)} method in specified system locale exclusively.
+         *
+         * @param res the resources to use. Pass current resources.
+         * @param newLocale the locale to change to
+         * @return the value returned from {@link #job(Resources)}.
+         */
+        public T runInLocale(final Resources res, final Locale newLocale) {
+            synchronized (sLockForRunInLocale) {
+                final Configuration conf = res.getConfiguration();
+                final Locale oldLocale = conf.locale;
+                try {
+                    if (newLocale != null && !newLocale.equals(oldLocale)) {
+                        conf.locale = newLocale;
+                        res.updateConfiguration(conf, res.getDisplayMetrics());
+                    }
+                    return job(res);
+                } finally {
+                    if (newLocale != null && !newLocale.equals(oldLocale)) {
+                        conf.locale = oldLocale;
+                        res.updateConfiguration(conf, res.getDisplayMetrics());
+                    }
+                }
+            }
+        }
     }
 
     private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
new file mode 100644
index 0000000..c5fb61f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.SharedPreferences;
+import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Logs the use of the LatinIME keyboard.
+ *
+ * This class logs operations on the IME keyboard, including what the user has typed.
+ * Data is stored locally in a file in app-specific storage.
+ *
+ * This functionality is off by default. See {@link ProductionFlag.IS_EXPERIMENTAL}.
+ */
+public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+    private static final String TAG = ResearchLogger.class.getSimpleName();
+    private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
+
+    private static final ResearchLogger sInstance = new ResearchLogger(new LogFileManager());
+    public static boolean sIsLogging = false;
+    /* package */ final Handler mLoggingHandler;
+    private InputMethodService mIms;
+    private final Date mDate;
+    private final SimpleDateFormat mDateFormat;
+
+    /**
+     * Isolates management of files. This variable should never be null, but can be changed
+     * to support testing.
+     */
+    private LogFileManager mLogFileManager;
+
+    /**
+     * Manages the file(s) that stores the logs.
+     *
+     * Handles creation, deletion, and provides Readers, Writers, and InputStreams to access
+     * the logs.
+     */
+    public static class LogFileManager {
+        private static final String DEFAULT_FILENAME = "log.txt";
+        private static final String DEFAULT_LOG_DIRECTORY = "researchLogger";
+
+        private static final long LOGFILE_PURGE_INTERVAL = 1000 * 60 * 60 * 24;
+
+        private InputMethodService mIms;
+        private File mFile;
+        private PrintWriter mPrintWriter;
+
+        /* package */ LogFileManager() {
+        }
+
+        public void init(InputMethodService ims) {
+            mIms = ims;
+        }
+
+        public synchronized void createLogFile() {
+            try {
+                createLogFile(DEFAULT_LOG_DIRECTORY, DEFAULT_FILENAME);
+            } catch (FileNotFoundException e) {
+                Log.w(TAG, e);
+            }
+        }
+
+        public synchronized void createLogFile(String dir, String filename)
+                throws FileNotFoundException {
+            if (mIms == null) {
+                Log.w(TAG, "InputMethodService is not configured.  Logging is off.");
+                return;
+            }
+            File filesDir = mIms.getFilesDir();
+            if (filesDir == null || !filesDir.exists()) {
+                Log.w(TAG, "Storage directory does not exist.  Logging is off.");
+                return;
+            }
+            File directory = new File(filesDir, dir);
+            if (!directory.exists()) {
+                boolean wasCreated = directory.mkdirs();
+                if (!wasCreated) {
+                    Log.w(TAG, "Log directory cannot be created.  Logging is off.");
+                    return;
+                }
+            }
+
+            close();
+            mFile = new File(directory, filename);
+            mFile.setReadable(false, false);
+            boolean append = true;
+            if (mFile.exists() && mFile.lastModified() + LOGFILE_PURGE_INTERVAL <
+                    System.currentTimeMillis()) {
+                append = false;
+            }
+            mPrintWriter = new PrintWriter(new FileOutputStream(mFile, append), true);
+        }
+
+        public synchronized boolean append(String s) {
+            if (mPrintWriter == null) {
+                Log.w(TAG, "PrintWriter is null");
+                return false;
+            } else {
+                mPrintWriter.print(s);
+                return !mPrintWriter.checkError();
+            }
+        }
+
+        public synchronized void reset() {
+            if (mPrintWriter != null) {
+                mPrintWriter.close();
+                mPrintWriter = null;
+            }
+            if (mFile != null && mFile.exists()) {
+                mFile.delete();
+                mFile = null;
+            }
+        }
+
+        public synchronized void close() {
+            if (mPrintWriter != null) {
+                mPrintWriter.close();
+                mPrintWriter = null;
+                mFile = null;
+            }
+        }
+    }
+
+    private ResearchLogger(LogFileManager logFileManager) {
+        mDate = new Date();
+        mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
+
+        HandlerThread handlerThread = new HandlerThread("ResearchLogger logging task",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        handlerThread.start();
+        mLoggingHandler = new Handler(handlerThread.getLooper());
+        mLogFileManager = logFileManager;
+    }
+
+    public static ResearchLogger getInstance() {
+        return sInstance;
+    }
+
+    public static void init(InputMethodService ims, SharedPreferences prefs) {
+        sInstance.initInternal(ims, prefs);
+    }
+
+    public void initInternal(InputMethodService ims, SharedPreferences prefs) {
+        mIms = ims;
+        if (mLogFileManager != null) {
+            mLogFileManager.init(ims);
+            mLogFileManager.createLogFile();
+        }
+        if (prefs != null) {
+            sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
+            prefs.registerOnSharedPreferenceChangeListener(this);
+        }
+    }
+
+    /**
+     * Change to a different logFileManager.
+     *
+     * @throws IllegalArgumentException if logFileManager is null
+     */
+    void setLogFileManager(LogFileManager manager) {
+        if (manager == null) {
+            throw new IllegalArgumentException("warning: trying to set null logFileManager");
+        } else {
+            mLogFileManager = manager;
+        }
+    }
+
+    /**
+     * Represents a category of logging events that share the same subfield structure.
+     */
+    private static enum LogGroup {
+        MOTION_EVENT("m"),
+        KEY("k"),
+        CORRECTION("c"),
+        STATE_CHANGE("s");
+
+        private final String mLogString;
+
+        private LogGroup(String logString) {
+            mLogString = logString;
+        }
+    }
+
+    public void logMotionEvent(final int action, final long eventTime, final int id,
+            final int x, final int y, final float size, final float pressure) {
+        final String eventTag;
+        switch (action) {
+            case MotionEvent.ACTION_CANCEL: eventTag = "[Cancel]"; break;
+            case MotionEvent.ACTION_UP: eventTag = "[Up]"; break;
+            case MotionEvent.ACTION_DOWN: eventTag = "[Down]"; break;
+            case MotionEvent.ACTION_POINTER_UP: eventTag = "[PointerUp]"; break;
+            case MotionEvent.ACTION_POINTER_DOWN: eventTag = "[PointerDown]"; break;
+            case MotionEvent.ACTION_MOVE: eventTag = "[Move]"; break;
+            case MotionEvent.ACTION_OUTSIDE: eventTag = "[Outside]"; break;
+            default: eventTag = "[Action" + action + "]"; break;
+        }
+        if (!TextUtils.isEmpty(eventTag)) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(eventTag);
+            sb.append('\t'); sb.append(eventTime);
+            sb.append('\t'); sb.append(id);
+            sb.append('\t'); sb.append(x);
+            sb.append('\t'); sb.append(y);
+            sb.append('\t'); sb.append(size);
+            sb.append('\t'); sb.append(pressure);
+            write(LogGroup.MOTION_EVENT, sb.toString());
+        }
+    }
+
+    public void logKeyEvent(int code, int x, int y) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(Keyboard.printableCode(code));
+        sb.append('\t'); sb.append(x);
+        sb.append('\t'); sb.append(y);
+        write(LogGroup.KEY, sb.toString());
+    }
+
+    public void logCorrection(String subgroup, String before, String after, int position) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(subgroup);
+        sb.append('\t'); sb.append(before);
+        sb.append('\t'); sb.append(after);
+        sb.append('\t'); sb.append(position);
+        write(LogGroup.CORRECTION, sb.toString());
+    }
+
+    public void logStateChange(String subgroup, String details) {
+        write(LogGroup.STATE_CHANGE, subgroup + "\t" + details);
+    }
+
+    public static enum UnsLogGroup {
+        // TODO: expand to include one flag per log point
+        // TODO: support selective enabling of flags
+        ON_UPDATE_SELECTION;
+
+        public boolean isEnabled = true;
+    }
+
+    public static void logUnstructured(UnsLogGroup logGroup, String details) {
+    }
+
+    private void write(final LogGroup logGroup, final String log) {
+        // TODO: rewrite in native for better performance
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                final long currentTime = System.currentTimeMillis();
+                final long upTime = SystemClock.uptimeMillis();
+                final StringBuilder builder = new StringBuilder();
+                builder.append(currentTime);
+                builder.append('\t'); builder.append(upTime);
+                builder.append('\t'); builder.append(logGroup.mLogString);
+                builder.append('\t'); builder.append(log);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log);
+                }
+                if (mLogFileManager.append(builder.toString())) {
+                    // success
+                } else {
+                    if (LatinImeLogger.sDBG) {
+                        Log.w(TAG, "Unable to write to log.");
+                    }
+                }
+            }
+        });
+    }
+
+    public void clearAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (LatinImeLogger.sDBG) {
+                    Log.d(TAG, "Delete log file.");
+                }
+                mLogFileManager.reset();
+            }
+        });
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        if (key == null || prefs == null) {
+            return;
+        }
+        sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 773efe7..7b98a71 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -18,7 +18,6 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.app.Fragment;
 import android.app.backup.BackupManager;
 import android.content.Context;
@@ -34,288 +33,54 @@
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.util.Log;
 import android.view.View;
-import android.view.inputmethod.EditorInfo;
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
 
-import com.android.inputmethod.compat.CompatUtils;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.compat.VibratorCompatWrapper;
-import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethodcommon.InputMethodSettingsActivity;
 
-import java.util.Arrays;
-import java.util.Locale;
-
 public class Settings extends InputMethodSettingsActivity
-        implements SharedPreferences.OnSharedPreferenceChangeListener,
-        DialogInterface.OnDismissListener, OnPreferenceClickListener {
-    private static final String TAG = Settings.class.getSimpleName();
-
+        implements SharedPreferences.OnSharedPreferenceChangeListener {
     public static final boolean ENABLE_EXPERIMENTAL_SETTINGS = false;
 
-    public static final String PREF_GENERAL_SETTINGS_KEY = "general_settings";
+    // In the same order as xml/prefs.xml
+    public static final String PREF_GENERAL_SETTINGS = "general_settings";
+    public static final String PREF_AUTO_CAP = "auto_cap";
     public static final String PREF_VIBRATE_ON = "vibrate_on";
     public static final String PREF_SOUND_ON = "sound_on";
-    public static final String PREF_KEY_PREVIEW_POPUP_ON = "popup_on";
-    public static final String PREF_AUTO_CAP = "auto_cap";
-    public static final String PREF_SHOW_SETTINGS_KEY = "show_settings_key";
-    public static final String PREF_VOICE_SETTINGS_KEY = "voice_mode";
-    public static final String PREF_INPUT_LANGUAGE = "input_language";
-    public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
-    public static final String PREF_SUBTYPES = "subtype_settings";
-
+    public static final String PREF_POPUP_ON = "popup_on";
+    public static final String PREF_VOICE_MODE = "voice_mode";
+    public static final String PREF_CORRECTION_SETTINGS = "correction_settings";
     public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
-    public static final String PREF_CORRECTION_SETTINGS_KEY = "correction_settings";
-    public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
     public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
-    public static final String PREF_DEBUG_SETTINGS = "debug_settings";
-
-    public static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
-    public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
-
-    public static final String PREF_MISC_SETTINGS_KEY = "misc_settings";
-
+    public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
+    public static final String PREF_MISC_SETTINGS = "misc_settings";
+    public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
+    public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
+    public static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
+            "pref_suppress_language_switch_key";
+    public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
+            "pref_include_other_imes_in_language_switch_list";
     public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
             "pref_key_preview_popup_dismiss_delay";
-    public static final String PREF_KEY_USE_CONTACTS_DICT =
-            "pref_key_use_contacts_dict";
-    public static final String PREF_KEY_ENABLE_SPAN_INSERT =
-            "enable_span_insert";
-
-    public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
-
-    public static final String PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS =
+    public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
+    public static final String PREF_BIGRAM_SUGGESTION = "bigram_suggestion";
+    public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
+    public static final String PREF_KEY_ENABLE_SPAN_INSERT = "enable_span_insert";
+    public static final String PREF_VIBRATION_DURATION_SETTINGS =
             "pref_vibration_duration_settings";
-
     public static final String PREF_KEYPRESS_SOUND_VOLUME =
             "pref_keypress_sound_volume";
-    // Dialog ids
-    private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
 
-    public static class Values {
-        // From resources:
-        public final int mDelayUpdateOldSuggestions;
-        public final String mWordSeparators;
-        public final String mMagicSpaceStrippers;
-        public final String mMagicSpaceSwappers;
-        public final String mSuggestPuncs;
-        public final SuggestedWords mSuggestPuncList;
-        private final String mSymbolsExcludedFromWordSeparators;
+    public static final String PREF_INPUT_LANGUAGE = "input_language";
+    public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
+    public static final String PREF_DEBUG_SETTINGS = "debug_settings";
 
-        // From preferences:
-        public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
-        public final boolean mVibrateOn;
-        public final boolean mKeyPreviewPopupOn;
-        public final int mKeyPreviewPopupDismissDelay;
-        public final boolean mAutoCap;
-        public final boolean mAutoCorrectEnabled;
-        public final double mAutoCorrectionThreshold;
-        // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
-        public final boolean mBigramSuggestionEnabled;
-        // Prediction: use bigrams to predict the next word when there is no input for it yet
-        public final boolean mBigramPredictionEnabled;
-        public final boolean mUseContactsDict;
-        public final boolean mEnableSuggestionSpanInsertion;
-
-        private final boolean mShowSettingsKey;
-        private final boolean mVoiceKeyEnabled;
-        private final boolean mVoiceKeyOnMain;
-
-        public Values(final SharedPreferences prefs, final Context context,
-                final String localeStr) {
-            final Resources res = context.getResources();
-            final Locale savedLocale;
-            if (null != localeStr) {
-                final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
-                savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale);
-            } else {
-                savedLocale = null;
-            }
-
-            // Get the resources
-            mDelayUpdateOldSuggestions = res.getInteger(
-                    R.integer.config_delay_update_old_suggestions);
-            mMagicSpaceStrippers = res.getString(R.string.magic_space_stripping_symbols);
-            mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
-            String wordSeparators = mMagicSpaceStrippers + mMagicSpaceSwappers
-                    + res.getString(R.string.magic_space_promoting_symbols);
-            final String symbolsExcludedFromWordSeparators =
-                    res.getString(R.string.symbols_excluded_from_word_separators);
-            for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
-                wordSeparators = wordSeparators.replace(
-                        symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
-            }
-            mSymbolsExcludedFromWordSeparators = symbolsExcludedFromWordSeparators;
-            mWordSeparators = wordSeparators;
-            mSuggestPuncs = res.getString(R.string.suggested_punctuations);
-            // TODO: it would be nice not to recreate this each time we change the configuration
-            mSuggestPuncList = createSuggestPuncList(mSuggestPuncs);
-
-            // Get the settings preferences
-            final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator();
-            mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON,
-                    res.getBoolean(R.bool.config_default_vibration_enabled));
-            mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
-                    res.getBoolean(R.bool.config_default_sound_enabled));
-            mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
-            mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
-            mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
-            mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res);
-            mBigramSuggestionEnabled = mAutoCorrectEnabled
-                    && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
-            mBigramPredictionEnabled = mBigramSuggestionEnabled
-                    && isBigramPredictionEnabled(prefs, res);
-            mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res);
-            mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
-            mEnableSuggestionSpanInsertion =
-                    prefs.getBoolean(Settings.PREF_KEY_ENABLE_SPAN_INSERT, true);
-            final boolean defaultShowSettingsKey = res.getBoolean(
-                    R.bool.config_default_show_settings_key);
-            mShowSettingsKey = isShowSettingsKeyOption(res)
-                    ? prefs.getBoolean(Settings.PREF_SHOW_SETTINGS_KEY, defaultShowSettingsKey)
-                    : defaultShowSettingsKey;
-            final String voiceModeMain = res.getString(R.string.voice_mode_main);
-            final String voiceModeOff = res.getString(R.string.voice_mode_off);
-            final String voiceMode = prefs.getString(PREF_VOICE_SETTINGS_KEY, voiceModeMain);
-            mVoiceKeyEnabled = voiceMode != null && !voiceMode.equals(voiceModeOff);
-            mVoiceKeyOnMain = voiceMode != null && voiceMode.equals(voiceModeMain);
-
-            LocaleUtils.setSystemLocale(res, savedLocale);
-        }
-
-        public boolean isSuggestedPunctuation(int code) {
-            return mSuggestPuncs.contains(String.valueOf((char)code));
-        }
-
-        public boolean isWordSeparator(int code) {
-            return mWordSeparators.contains(String.valueOf((char)code));
-        }
-
-        public boolean isSymbolExcludedFromWordSeparators(int code) {
-            return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
-        }
-
-        public boolean isMagicSpaceStripper(int code) {
-            return mMagicSpaceStrippers.contains(String.valueOf((char)code));
-        }
-
-        public boolean isMagicSpaceSwapper(int code) {
-            return mMagicSpaceSwappers.contains(String.valueOf((char)code));
-        }
-
-        private static boolean isAutoCorrectEnabled(SharedPreferences sp, Resources resources) {
-            final String currentAutoCorrectionSetting = sp.getString(
-                    Settings.PREF_AUTO_CORRECTION_THRESHOLD,
-                    resources.getString(R.string.auto_correction_threshold_mode_index_modest));
-            final String autoCorrectionOff = resources.getString(
-                    R.string.auto_correction_threshold_mode_index_off);
-            return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
-        }
-
-        // Public to access from KeyboardSwitcher. Should it have access to some
-        // process-global instance instead?
-        public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) {
-            final boolean showPopupOption = resources.getBoolean(
-                    R.bool.config_enable_show_popup_on_keypress_option);
-            if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
-            return sp.getBoolean(Settings.PREF_KEY_PREVIEW_POPUP_ON,
-                    resources.getBoolean(R.bool.config_default_popup_preview));
-        }
-
-        // Likewise
-        public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp,
-                Resources resources) {
-            return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
-                    Integer.toString(resources.getInteger(R.integer.config_delay_after_preview))));
-        }
-
-        private static boolean isBigramSuggestionEnabled(SharedPreferences sp, Resources resources,
-                boolean autoCorrectEnabled) {
-            final boolean showBigramSuggestionsOption = resources.getBoolean(
-                    R.bool.config_enable_bigram_suggestions_option);
-            if (!showBigramSuggestionsOption) {
-                return autoCorrectEnabled;
-            }
-            return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, resources.getBoolean(
-                    R.bool.config_default_bigram_suggestions));
-        }
-
-        private static boolean isBigramPredictionEnabled(SharedPreferences sp,
-                Resources resources) {
-            return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
-                    R.bool.config_default_bigram_prediction));
-        }
-
-        private static double getAutoCorrectionThreshold(SharedPreferences sp,
-                Resources resources) {
-            final String currentAutoCorrectionSetting = sp.getString(
-                    Settings.PREF_AUTO_CORRECTION_THRESHOLD,
-                    resources.getString(R.string.auto_correction_threshold_mode_index_modest));
-            final String[] autoCorrectionThresholdValues = resources.getStringArray(
-                    R.array.auto_correction_threshold_values);
-            // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
-            double autoCorrectionThreshold = Double.MAX_VALUE;
-            try {
-                final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
-                if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
-                    autoCorrectionThreshold = Double.parseDouble(
-                            autoCorrectionThresholdValues[arrayIndex]);
-                }
-            } catch (NumberFormatException e) {
-                // Whenever the threshold settings are correct, never come here.
-                autoCorrectionThreshold = Double.MAX_VALUE;
-                Log.w(TAG, "Cannot load auto correction threshold setting."
-                        + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
-                        + ", autoCorrectionThresholdValues: "
-                        + Arrays.toString(autoCorrectionThresholdValues));
-            }
-            return autoCorrectionThreshold;
-        }
-
-        private static SuggestedWords createSuggestPuncList(final String puncs) {
-            SuggestedWords.Builder builder = new SuggestedWords.Builder();
-            if (puncs != null) {
-                for (int i = 0; i < puncs.length(); i++) {
-                    builder.addWord(puncs.subSequence(i, i + 1));
-                }
-            }
-            return builder.setIsPunctuationSuggestions().build();
-        }
-
-        public static boolean isShowSettingsKeyOption(final Resources resources) {
-            return resources.getBoolean(R.bool.config_enable_show_settings_key_option);
-
-        }
-
-        public boolean isSettingsKeyEnabled() {
-            return mShowSettingsKey;
-        }
-
-        public boolean isVoiceKeyEnabled(EditorInfo attribute) {
-            final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
-            final int inputType = (attribute != null) ? attribute.inputType : 0;
-            return shortcutImeEnabled && mVoiceKeyEnabled
-                    && !InputTypeCompatUtils.isPasswordInputType(inputType);
-        }
-
-        public boolean isVoiceKeyOnMain() {
-            return mVoiceKeyOnMain;
-        }
-    }
-
-    private PreferenceScreen mInputLanguageSelection;
     private PreferenceScreen mKeypressVibrationDurationSettingsPref;
     private PreferenceScreen mKeypressSoundVolumeSettingsPref;
     private ListPreference mVoicePreference;
-    private CheckBoxPreference mShowSettingsKeyPreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThresholdPreference;
     private ListPreference mKeyPreviewPopupDismissDelay;
@@ -324,15 +89,10 @@
     // Prediction: use bigrams to predict the next word when there is no input for it yet
     private CheckBoxPreference mBigramPrediction;
     private Preference mDebugSettingsPreference;
-    private boolean mVoiceOn;
 
-    private AlertDialog mDialog;
     private TextView mKeypressVibrationDurationSettingsTextView;
     private TextView mKeypressSoundVolumeSettingsTextView;
 
-    private boolean mOkClicked = false;
-    private String mVoiceModeOff;
-
     private void ensureConsistencyOfAutoCorrectionSettings() {
         final String autoCorrectionOff = getResources().getString(
                 R.string.auto_correction_threshold_mode_index_off);
@@ -363,22 +123,15 @@
         final Context context = getActivityInternal();
 
         addPreferencesFromResource(R.xml.prefs);
-        mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
-        mInputLanguageSelection.setOnPreferenceClickListener(this);
-        mVoicePreference = (ListPreference) findPreference(PREF_VOICE_SETTINGS_KEY);
-        mShowSettingsKeyPreference = (CheckBoxPreference) findPreference(PREF_SHOW_SETTINGS_KEY);
+        mVoicePreference = (ListPreference) findPreference(PREF_VOICE_MODE);
         mShowCorrectionSuggestionsPreference =
                 (ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
         SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
         prefs.registerOnSharedPreferenceChangeListener(this);
 
-        mVoiceModeOff = getString(R.string.voice_mode_off);
-        mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
-                .equals(mVoiceModeOff));
-
         mAutoCorrectionThresholdPreference =
                 (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
-        mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
+        mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTION);
         mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
         mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
         if (mDebugSettingsPreference != null) {
@@ -391,15 +144,11 @@
         ensureConsistencyOfAutoCorrectionSettings();
 
         final PreferenceGroup generalSettings =
-                (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS_KEY);
+                (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS);
         final PreferenceGroup textCorrectionGroup =
-                (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY);
+                (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS);
         final PreferenceGroup miscSettings =
-                (PreferenceGroup) findPreference(PREF_MISC_SETTINGS_KEY);
-
-        if (!Values.isShowSettingsKeyOption(res)) {
-            generalSettings.removePreference(mShowSettingsKeyPreference);
-        }
+                (PreferenceGroup) findPreference(PREF_MISC_SETTINGS);
 
         final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
@@ -407,18 +156,14 @@
             generalSettings.removePreference(mVoicePreference);
         }
 
-        if (!VibratorCompatWrapper.getInstance(context).hasVibrator()) {
+        if (!VibratorUtils.getInstance(context).hasVibrator()) {
             generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
         }
 
-        if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
-            generalSettings.removePreference(findPreference(PREF_SUBTYPES));
-        }
-
         final boolean showPopupOption = res.getBoolean(
                 R.bool.config_enable_show_popup_on_keypress_option);
         if (!showPopupOption) {
-            generalSettings.removePreference(findPreference(PREF_KEY_PREVIEW_POPUP_ON));
+            generalSettings.removePreference(findPreference(PREF_POPUP_ON));
         }
 
         final boolean showBigramSuggestionsOption = res.getBoolean(
@@ -430,6 +175,11 @@
             }
         }
 
+        final CheckBoxPreference includeOtherImesInLanguageSwitchList =
+                (CheckBoxPreference)findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
+        includeOtherImesInLanguageSwitchList.setEnabled(
+                !SettingsValues.isLanguageSwitchKeySupressed(prefs));
+
         mKeyPreviewPopupDismissDelay =
                 (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
         final String[] entries = new String[] {
@@ -437,14 +187,15 @@
                 res.getString(R.string.key_preview_popup_dismiss_default_delay),
         };
         final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
-                R.integer.config_delay_after_preview));
+                R.integer.config_key_preview_linger_timeout));
         mKeyPreviewPopupDismissDelay.setEntries(entries);
         mKeyPreviewPopupDismissDelay.setEntryValues(
                 new String[] { "0", popupDismissDelayDefaultValue });
         if (null == mKeyPreviewPopupDismissDelay.getValue()) {
             mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
         }
-        mKeyPreviewPopupDismissDelay.setEnabled(Values.isKeyPreviewPopupEnabled(prefs, res));
+        mKeyPreviewPopupDismissDelay.setEnabled(
+                SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
 
         final PreferenceScreen dictionaryLink =
                 (PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY);
@@ -455,17 +206,25 @@
             textCorrectionGroup.removePreference(dictionaryLink);
         }
 
-        final boolean showUsabilityModeStudyOption = res.getBoolean(
-                R.bool.config_enable_usability_study_mode_option);
-        if (!showUsabilityModeStudyOption || !ENABLE_EXPERIMENTAL_SETTINGS) {
-            final Preference pref = findPreference(PREF_USABILITY_STUDY_MODE);
-            if (pref != null) {
-                miscSettings.removePreference(pref);
+        final boolean showUsabilityStudyModeOption =
+                res.getBoolean(R.bool.config_enable_usability_study_mode_option)
+                        || ProductionFlag.IS_EXPERIMENTAL || ENABLE_EXPERIMENTAL_SETTINGS;
+        final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
+        if (!showUsabilityStudyModeOption) {
+            if (usabilityStudyPref != null) {
+                miscSettings.removePreference(usabilityStudyPref);
+            }
+        }
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            if (usabilityStudyPref instanceof CheckBoxPreference) {
+                CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
+                checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, true));
+                checkbox.setSummary(R.string.settings_warning_researcher_mode);
             }
         }
 
         mKeypressVibrationDurationSettingsPref =
-                (PreferenceScreen) findPreference(PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS);
+                (PreferenceScreen) findPreference(PREF_VIBRATION_DURATION_SETTINGS);
         if (mKeypressVibrationDurationSettingsPref != null) {
             mKeypressVibrationDurationSettingsPref.setOnPreferenceClickListener(
                     new OnPreferenceClickListener() {
@@ -499,9 +258,7 @@
     public void onResume() {
         super.onResume();
         final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
-        if (isShortcutImeEnabled
-                || (VoiceProxy.VOICE_INSTALLED
-                        && VoiceProxy.isRecognitionAvailable(getActivityInternal()))) {
+        if (isShortcutImeEnabled) {
             updateVoiceModeSummary();
         } else {
             getPreferenceScreen().removePreference(mVoicePreference);
@@ -520,40 +277,26 @@
     @Override
     public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
         (new BackupManager(getActivityInternal())).dataChanged();
-        // If turning on voice input, show dialog
-        if (key.equals(PREF_VOICE_SETTINGS_KEY) && !mVoiceOn) {
-            if (!prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
-                    .equals(mVoiceModeOff)) {
-                showVoiceConfirmation();
-            }
-        } else if (key.equals(PREF_KEY_PREVIEW_POPUP_ON)) {
+        if (key.equals(PREF_POPUP_ON)) {
             final ListPreference popupDismissDelay =
                 (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
             if (null != popupDismissDelay) {
-                popupDismissDelay.setEnabled(prefs.getBoolean(PREF_KEY_PREVIEW_POPUP_ON, true));
+                popupDismissDelay.setEnabled(prefs.getBoolean(PREF_POPUP_ON, true));
             }
+        } else if (key.equals(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
+            final CheckBoxPreference includeOtherImesInLanguageSwicthList =
+                    (CheckBoxPreference)findPreference(
+                            PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
+            includeOtherImesInLanguageSwicthList.setEnabled(
+                    !SettingsValues.isLanguageSwitchKeySupressed(prefs));
         }
         ensureConsistencyOfAutoCorrectionSettings();
-        mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
-                .equals(mVoiceModeOff));
         updateVoiceModeSummary();
         updateShowCorrectionSuggestionsSummary();
         updateKeyPreviewPopupDelaySummary();
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
     }
 
-    @Override
-    public boolean onPreferenceClick(Preference pref) {
-        if (pref == mInputLanguageSelection) {
-            startActivity(CompatUtils.getInputLanguageSelectionIntent(
-                    Utils.getInputMethodId(
-                            InputMethodManagerCompatWrapper.getInstance(),
-                            getActivityInternal().getApplicationInfo().packageName), 0));
-            return true;
-        }
-        return false;
-    }
-
     private void updateShowCorrectionSuggestionsSummary() {
         mShowCorrectionSuggestionsPreference.setSummary(
                 getResources().getStringArray(R.array.prefs_suggestion_visibilities)
@@ -566,84 +309,16 @@
         lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
     }
 
-    private void showVoiceConfirmation() {
-        mOkClicked = false;
-        getActivityInternal().showDialog(VOICE_INPUT_CONFIRM_DIALOG);
-        // Make URL in the dialog message clickable
-        if (mDialog != null) {
-            TextView textView = (TextView) mDialog.findViewById(android.R.id.message);
-            if (textView != null) {
-                textView.setMovementMethod(LinkMovementMethod.getInstance());
-            }
-        }
-    }
-
     private void updateVoiceModeSummary() {
         mVoicePreference.setSummary(
                 getResources().getStringArray(R.array.voice_input_modes_summary)
                 [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
     }
 
-    @Override
-    protected Dialog onCreateDialog(int id) {
-        switch (id) {
-            case VOICE_INPUT_CONFIRM_DIALOG:
-                DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int whichButton) {
-                        if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
-                            mVoicePreference.setValue(mVoiceModeOff);
-                        } else if (whichButton == DialogInterface.BUTTON_POSITIVE) {
-                            mOkClicked = true;
-                        }
-                    }
-                };
-                AlertDialog.Builder builder = new AlertDialog.Builder(getActivityInternal())
-                        .setTitle(R.string.voice_warning_title)
-                        .setPositiveButton(android.R.string.ok, listener)
-                        .setNegativeButton(android.R.string.cancel, listener);
-
-                // Get the current list of supported locales and check the current locale against
-                // that list, to decide whether to put a warning that voice input will not work in
-                // the current language as part of the pop-up confirmation dialog.
-                boolean localeSupported = SubtypeSwitcher.isVoiceSupported(
-                        this, Locale.getDefault().toString());
-
-                final CharSequence message;
-                if (localeSupported) {
-                    message = TextUtils.concat(
-                            getText(R.string.voice_warning_may_not_understand), "\n\n",
-                                    getText(R.string.voice_hint_dialog_message));
-                } else {
-                    message = TextUtils.concat(
-                            getText(R.string.voice_warning_locale_not_supported), "\n\n",
-                                    getText(R.string.voice_warning_may_not_understand), "\n\n",
-                                            getText(R.string.voice_hint_dialog_message));
-                }
-                builder.setMessage(message);
-                AlertDialog dialog = builder.create();
-                mDialog = dialog;
-                dialog.setOnDismissListener(this);
-                return dialog;
-            default:
-                Log.e(TAG, "unknown dialog " + id);
-                return null;
-        }
-    }
-
-    @Override
-    public void onDismiss(DialogInterface dialog) {
-        if (!mOkClicked) {
-            // This assumes that onPreferenceClick gets called first, and this if the user
-            // agreed after the warning, we set the mOkClicked value to true.
-            mVoicePreference.setValue(mVoiceModeOff);
-        }
-    }
-
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
             SharedPreferences sp, Resources res) {
         if (mKeypressVibrationDurationSettingsPref != null) {
-            final boolean hasVibrator = VibratorCompatWrapper.getInstance(this).hasVibrator();
+            final boolean hasVibrator = VibratorUtils.getInstance(this).hasVibrator();
             final boolean vibrateOn = hasVibrator && sp.getBoolean(Settings.PREF_VIBRATE_ON,
                     res.getBoolean(R.bool.config_default_vibration_enabled));
             mKeypressVibrationDurationSettingsPref.setEnabled(vibrateOn);
@@ -660,7 +335,7 @@
             SharedPreferences sp, Resources res) {
         if (mKeypressVibrationDurationSettingsPref != null) {
             mKeypressVibrationDurationSettingsPref.setSummary(
-                    Utils.getCurrentVibrationDuration(sp, res)
+                    SettingsValues.getCurrentVibrationDuration(sp, res)
                             + res.getString(R.string.settings_ms));
         }
     }
@@ -676,7 +351,7 @@
             public void onClick(DialogInterface dialog, int whichButton) {
                 final int ms = Integer.valueOf(
                         mKeypressVibrationDurationSettingsTextView.getText().toString());
-                sp.edit().putInt(Settings.PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS, ms).apply();
+                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
                 updateKeypressVibrationDurationSettingsSummary(sp, res);
             }
         });
@@ -688,7 +363,7 @@
         });
         final View v = context.getLayoutInflater().inflate(
                 R.layout.vibration_settings_dialog, null);
-        final int currentMs = Utils.getCurrentVibrationDuration(
+        final int currentMs = SettingsValues.getCurrentVibrationDuration(
                 getPreferenceManager().getSharedPreferences(), getResources());
         mKeypressVibrationDurationSettingsTextView = (TextView)v.findViewById(R.id.vibration_value);
         final SeekBar sb = (SeekBar)v.findViewById(R.id.vibration_settings);
@@ -706,7 +381,7 @@
             @Override
             public void onStopTrackingTouch(SeekBar arg0) {
                 final int tempMs = arg0.getProgress();
-                VibratorCompatWrapper.getInstance(context).vibrate(tempMs);
+                VibratorUtils.getInstance(context).vibrate(tempMs);
             }
         });
         sb.setProgress(currentMs);
@@ -717,8 +392,8 @@
 
     private void updateKeypressSoundVolumeSummary(SharedPreferences sp, Resources res) {
         if (mKeypressSoundVolumeSettingsPref != null) {
-            mKeypressSoundVolumeSettingsPref.setSummary(
-                    String.valueOf((int)(Utils.getCurrentKeypressSoundVolume(sp, res) * 100)));
+            mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
+                    (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100)));
         }
     }
 
@@ -747,8 +422,8 @@
         });
         final View v = context.getLayoutInflater().inflate(
                 R.layout.sound_effect_volume_dialog, null);
-        final int currentVolumeInt = (int)(Utils.getCurrentKeypressSoundVolume(
-                getPreferenceManager().getSharedPreferences(), getResources()) * 100);
+        final int currentVolumeInt =
+                (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100);
         mKeypressSoundVolumeSettingsTextView =
                 (TextView)v.findViewById(R.id.sound_effect_volume_value);
         final SeekBar sb = (SeekBar)v.findViewById(R.id.sound_effect_volume_bar);
@@ -774,4 +449,4 @@
         builder.setView(v);
         builder.create().show();
     }
-}
\ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
new file mode 100644
index 0000000..1036784
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.keyboard.internal.KeySpecParser;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * When you call the constructor of this class, you may want to change the current system locale by
+ * using {@link LocaleUtils.RunInLocale}.
+ */
+public class SettingsValues {
+    private static final String TAG = SettingsValues.class.getSimpleName();
+
+    // From resources:
+    public final int mDelayUpdateOldSuggestions;
+    public final String mWeakSpaceStrippers;
+    public final String mWeakSpaceSwappers;
+    private final String mPhantomSpacePromotingSymbols;
+    public final SuggestedWords mSuggestPuncList;
+    private final String mSymbolsExcludedFromWordSeparators;
+    public final String mWordSeparators;
+    public final CharSequence mHintToSaveText;
+
+    // From preferences, in the same order as xml/prefs.xml:
+    public final boolean mAutoCap;
+    public final boolean mVibrateOn;
+    public final boolean mSoundOn;
+    public final boolean mKeyPreviewPopupOn;
+    private final String mVoiceMode;
+    private final String mAutoCorrectionThresholdRawValue;
+    public final String mShowSuggestionsSetting;
+    @SuppressWarnings("unused") // TODO: Use this
+    private final boolean mUsabilityStudyMode;
+    public final boolean mIncludesOtherImesInLanguageSwitchList;
+    public final boolean mIsLanguageSwitchKeySuppressed;
+    @SuppressWarnings("unused") // TODO: Use this
+    private final String mKeyPreviewPopupDismissDelayRawValue;
+    public final boolean mUseContactsDict;
+    // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
+    public final boolean mBigramSuggestionEnabled;
+    // Prediction: use bigrams to predict the next word when there is no input for it yet
+    public final boolean mBigramPredictionEnabled;
+    public final boolean mEnableSuggestionSpanInsertion;
+    @SuppressWarnings("unused") // TODO: Use this
+    private final int mVibrationDurationSettingsRawValue;
+    @SuppressWarnings("unused") // TODO: Use this
+    private final float mKeypressSoundVolumeRawValue;
+
+    // Deduced settings
+    public final int mKeypressVibrationDuration;
+    public final float mFxVolume;
+    public final int mKeyPreviewPopupDismissDelay;
+    public final boolean mAutoCorrectEnabled;
+    public final double mAutoCorrectionThreshold;
+    private final boolean mVoiceKeyEnabled;
+    private final boolean mVoiceKeyOnMain;
+
+    public SettingsValues(final SharedPreferences prefs, final Context context) {
+        final Resources res = context.getResources();
+
+        // Get the resources
+        mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
+        mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols);
+        mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols);
+        mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols);
+        if (LatinImeLogger.sDBG) {
+            final int length = mWeakSpaceStrippers.length();
+            for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) {
+                if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) {
+                    throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i)
+                            + " is both a weak space swapper and stripper.");
+                }
+            }
+        }
+        final String[] suggestPuncsSpec = KeySpecParser.parseCsvString(
+                res.getString(R.string.suggested_punctuations), res, R.string.english_ime_name);
+        mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
+        mSymbolsExcludedFromWordSeparators =
+                res.getString(R.string.symbols_excluded_from_word_separators);
+        mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers,
+                mSymbolsExcludedFromWordSeparators, res);
+        mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
+
+        // Get the settings preferences
+        mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
+        mVibrateOn = isVibrateOn(context, prefs, res);
+        mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+                res.getBoolean(R.bool.config_default_sound_enabled));
+        mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
+        final String voiceModeMain = res.getString(R.string.voice_mode_main);
+        final String voiceModeOff = res.getString(R.string.voice_mode_off);
+        mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain);
+        mAutoCorrectionThresholdRawValue = prefs.getString(Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+                res.getString(R.string.auto_correction_threshold_mode_index_modest));
+        mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING,
+                res.getString(R.string.prefs_suggestion_visibility_default_value));
+        mUsabilityStudyMode = getUsabilityStudyMode(prefs);
+        mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
+                Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
+        mIsLanguageSwitchKeySuppressed = isLanguageSwitchKeySupressed(prefs);
+        mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
+                Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+                Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
+        mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
+        mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue);
+        mBigramSuggestionEnabled = mAutoCorrectEnabled
+                && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
+        mBigramPredictionEnabled = mBigramSuggestionEnabled
+                && isBigramPredictionEnabled(prefs, res);
+        mEnableSuggestionSpanInsertion =
+                prefs.getBoolean(Settings.PREF_KEY_ENABLE_SPAN_INSERT, true);
+        mVibrationDurationSettingsRawValue =
+                prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
+        mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+
+        // Compute other readable settings
+        mKeypressVibrationDuration = getCurrentVibrationDuration(prefs, res);
+        mFxVolume = getCurrentKeypressSoundVolume(prefs, res);
+        mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
+        mAutoCorrectionThreshold = getAutoCorrectionThreshold(res,
+                mAutoCorrectionThresholdRawValue);
+        mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
+        mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
+    }
+
+    // Helper functions to create member values.
+    private static SuggestedWords createSuggestPuncList(final String[] puncs) {
+        final ArrayList<SuggestedWords.SuggestedWordInfo> puncList =
+                new ArrayList<SuggestedWords.SuggestedWordInfo>();
+        if (puncs != null) {
+            for (final String puncSpec : puncs) {
+                puncList.add(new SuggestedWords.SuggestedWordInfo(
+                        KeySpecParser.getLabel(puncSpec), SuggestedWordInfo.MAX_SCORE));
+            }
+        }
+        return new SuggestedWords(puncList,
+                false /* typedWordValid */,
+                false /* hasAutoCorrectionCandidate */,
+                false /* allowsToBeAutoCorrected */,
+                true /* isPunctuationSuggestions */,
+                false /* isObsoleteSuggestions */);
+    }
+
+    private static String createWordSeparators(final String weakSpaceStrippers,
+            final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators,
+            final Resources res) {
+        String wordSeparators = weakSpaceStrippers + weakSpaceSwappers
+                + res.getString(R.string.phantom_space_promoting_symbols);
+        for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
+            wordSeparators = wordSeparators.replace(
+                    symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
+        }
+        return wordSeparators;
+    }
+
+    private static boolean isVibrateOn(final Context context, final SharedPreferences prefs,
+            final Resources res) {
+        final boolean hasVibrator = VibratorUtils.getInstance(context).hasVibrator();
+        return hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON,
+                res.getBoolean(R.bool.config_default_vibration_enabled));
+    }
+
+    public boolean isWordSeparator(int code) {
+        return mWordSeparators.contains(String.valueOf((char)code));
+    }
+
+    public boolean isSymbolExcludedFromWordSeparators(int code) {
+        return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
+    }
+
+    public boolean isWeakSpaceStripper(int code) {
+        // TODO: this does not work if the code does not fit in a char
+        return mWeakSpaceStrippers.contains(String.valueOf((char)code));
+    }
+
+    public boolean isWeakSpaceSwapper(int code) {
+        // TODO: this does not work if the code does not fit in a char
+        return mWeakSpaceSwappers.contains(String.valueOf((char)code));
+    }
+
+    public boolean isPhantomSpacePromotingSymbol(int code) {
+        // TODO: this does not work if the code does not fit in a char
+        return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code));
+    }
+
+    private static boolean isAutoCorrectEnabled(final Resources resources,
+            final String currentAutoCorrectionSetting) {
+        final String autoCorrectionOff = resources.getString(
+                R.string.auto_correction_threshold_mode_index_off);
+        return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
+    }
+
+    // Public to access from KeyboardSwitcher. Should it have access to some
+    // process-global instance instead?
+    public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) {
+        final boolean showPopupOption = resources.getBoolean(
+                R.bool.config_enable_show_popup_on_keypress_option);
+        if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
+        return sp.getBoolean(Settings.PREF_POPUP_ON,
+                resources.getBoolean(R.bool.config_default_popup_preview));
+    }
+
+    // Likewise
+    public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp,
+            Resources resources) {
+        // TODO: use mKeyPreviewPopupDismissDelayRawValue instead of reading it again here.
+        return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+                Integer.toString(resources.getInteger(
+                        R.integer.config_key_preview_linger_timeout))));
+    }
+
+    private static boolean isBigramSuggestionEnabled(final SharedPreferences sp,
+            final Resources resources, final boolean autoCorrectEnabled) {
+        final boolean showBigramSuggestionsOption = resources.getBoolean(
+                R.bool.config_enable_bigram_suggestions_option);
+        if (!showBigramSuggestionsOption) {
+            return autoCorrectEnabled;
+        }
+        return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTION, resources.getBoolean(
+                R.bool.config_default_bigram_suggestions));
+    }
+
+    private static boolean isBigramPredictionEnabled(final SharedPreferences sp,
+            final Resources resources) {
+        return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
+                R.bool.config_default_bigram_prediction));
+    }
+
+    private static double getAutoCorrectionThreshold(final Resources resources,
+            final String currentAutoCorrectionSetting) {
+        final String[] autoCorrectionThresholdValues = resources.getStringArray(
+                R.array.auto_correction_threshold_values);
+        // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
+        double autoCorrectionThreshold = Double.MAX_VALUE;
+        try {
+            final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
+            if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
+                autoCorrectionThreshold = Double.parseDouble(
+                        autoCorrectionThresholdValues[arrayIndex]);
+            }
+        } catch (NumberFormatException e) {
+            // Whenever the threshold settings are correct, never come here.
+            autoCorrectionThreshold = Double.MAX_VALUE;
+            Log.w(TAG, "Cannot load auto correction threshold setting."
+                    + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
+                    + ", autoCorrectionThresholdValues: "
+                    + Arrays.toString(autoCorrectionThresholdValues));
+        }
+        return autoCorrectionThreshold;
+    }
+
+    public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
+        final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+        final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
+        return shortcutImeEnabled && mVoiceKeyEnabled
+                && !InputTypeUtils.isPasswordInputType(inputType);
+    }
+
+    public boolean isVoiceKeyOnMain() {
+        return mVoiceKeyOnMain;
+    }
+
+    public static boolean isLanguageSwitchKeySupressed(SharedPreferences sp) {
+        return sp.getBoolean(Settings.PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
+    }
+
+    public boolean isLanguageSwitchKeyEnabled(Context context) {
+        if (mIsLanguageSwitchKeySuppressed) {
+            return false;
+        }
+        if (mIncludesOtherImesInLanguageSwitchList) {
+            return SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(/* include aux subtypes */false);
+        } else {
+            return SubtypeUtils.hasMultipleEnabledSubtypesInThisIme(
+                    context, /* include aux subtypes */false);
+        }
+    }
+
+    public boolean isFullscreenModeAllowed(Resources res) {
+        return res.getBoolean(R.bool.config_use_fullscreen_mode);
+    }
+
+    // Accessed from the settings interface, hence public
+    public static float getCurrentKeypressSoundVolume(final SharedPreferences sp,
+                final Resources res) {
+        // TODO: use mVibrationDurationSettingsRawValue instead of reading it again here
+        final float volume = sp.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+        if (volume >= 0) {
+            return volume;
+        }
+
+        return Float.parseFloat(
+                Utils.getDeviceOverrideValue(res, R.array.keypress_volumes, "-1.0f"));
+    }
+
+    // Likewise
+    public static int getCurrentVibrationDuration(final SharedPreferences sp,
+                final Resources res) {
+        // TODO: use mKeypressVibrationDuration instead of reading it again here
+        final int ms = sp.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
+        if (ms >= 0) {
+            return ms;
+        }
+
+        return Integer.parseInt(
+                Utils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations, "-1"));
+    }
+
+    // Likewise
+    public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
+        // TODO: use mUsabilityStudyMode instead of reading it again here
+        return prefs.getBoolean(Settings.PREF_USABILITY_STUDY_MODE, true);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/StringBuilderPool.java b/java/src/com/android/inputmethod/latin/StringBuilderPool.java
deleted file mode 100644
index a663ed4..0000000
--- a/java/src/com/android/inputmethod/latin/StringBuilderPool.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A pool of string builders to be used from anywhere.
- */
-public class StringBuilderPool {
-    // Singleton
-    private static final StringBuilderPool sInstance = new StringBuilderPool();
-    private static final boolean DEBUG = false;
-    private StringBuilderPool() {}
-    // TODO: Make this a normal array with a size of 20, or a ConcurrentQueue
-    private final List<StringBuilder> mPool =
-            Collections.synchronizedList(new ArrayList<StringBuilder>());
-
-    public static StringBuilder getStringBuilder(final int initialSize) {
-        // TODO: although the pool is synchronized, the following is not thread-safe.
-        // Two threads entering this at the same time could take the same size of the pool and the
-        // second to attempt removing this index from the pool would crash with an
-        // IndexOutOfBoundsException.
-        // At the moment this pool is only used in Suggest.java and only in one thread so it's
-        // okay. The simplest thing to do here is probably to replace the ArrayList with a
-        // ConcurrentQueue.
-        final int poolSize = sInstance.mPool.size();
-        final StringBuilder sb = poolSize > 0 ? (StringBuilder) sInstance.mPool.remove(poolSize - 1)
-                : new StringBuilder(initialSize);
-        sb.setLength(0);
-        return sb;
-    }
-
-    public static void recycle(final StringBuilder garbage) {
-        if (DEBUG) {
-            final int gid = garbage.hashCode();
-            for (final StringBuilder q : sInstance.mPool) {
-                if (gid == q.hashCode()) throw new RuntimeException("Duplicate id " + gid);
-            }
-        }
-        sInstance.mPool.add(garbage);
-    }
-
-    public static void ensureCapacity(final int capacity, final int initialSize) {
-        for (int i = sInstance.mPool.size(); i < capacity; ++i) {
-            final StringBuilder sb = new StringBuilder(initialSize);
-            sInstance.mPool.add(sb);
-        }
-    }
-
-    public static int getSize() {
-        return sInstance.mPool.size();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
new file mode 100644
index 0000000..7b34cae
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class StringUtils {
+    private StringUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static boolean canBeFollowedByPeriod(final int codePoint) {
+        // TODO: Check again whether there really ain't a better way to check this.
+        // TODO: This should probably be language-dependant...
+        return Character.isLetterOrDigit(codePoint)
+                || codePoint == Keyboard.CODE_SINGLE_QUOTE
+                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+    }
+
+    public static int codePointCount(String text) {
+        if (TextUtils.isEmpty(text)) return 0;
+        return text.codePointCount(0, text.length());
+    }
+
+    public static boolean containsInCsv(String key, String csv) {
+        if (csv == null)
+            return false;
+        for (String option : csv.split(",")) {
+            if (option.equals(key))
+                return true;
+        }
+        return false;
+    }
+
+    public static boolean inPrivateImeOptions(String packageName, String key,
+            EditorInfo editorInfo) {
+        if (editorInfo == null)
+            return false;
+        return containsInCsv(packageName != null ? packageName + "." + key : key,
+                editorInfo.privateImeOptions);
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the character.
+     * @param a first character to check
+     * @param b second character to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(char a, char b) {
+        // Some language, such as Turkish, need testing both cases.
+        return a == b
+                || Character.toLowerCase(a) == Character.toLowerCase(b)
+                || Character.toUpperCase(a) == Character.toUpperCase(b);
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if they are
+     * both null.
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+        if (a == b)
+            return true;  // including both a and b are null.
+        if (a == null || b == null)
+            return false;
+        final int length = a.length();
+        if (length != b.length())
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+     * and b is zero length.
+     * @param a CharSequence to check
+     * @param b character array to check
+     * @param offset start offset of array b
+     * @param length length of characters in array b
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     * @throws IndexOutOfBoundsException
+     *   if {@code offset < 0 || length < 0 || offset + length > data.length}.
+     * @throws NullPointerException if {@code b == null}.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+        if (offset < 0 || length < 0 || length > b.length - offset)
+            throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+                    + " length=" + length);
+        if (a == null)
+            return length == 0;  // including a is null and b is zero length.
+        if (a.length() != length)
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Remove duplicates from an array of strings.
+     *
+     * This method will always keep the first occurence of all strings at their position
+     * in the array, removing the subsequent ones.
+     */
+    public static void removeDupes(final ArrayList<CharSequence> suggestions) {
+        if (suggestions.size() < 2) return;
+        int i = 1;
+        // Don't cache suggestions.size(), since we may be removing items
+        while (i < suggestions.size()) {
+            final CharSequence cur = suggestions.get(i);
+            // Compare each suggestion with each previous suggestion
+            for (int j = 0; j < i; j++) {
+                CharSequence previous = suggestions.get(j);
+                if (TextUtils.equals(cur, previous)) {
+                    suggestions.remove(i);
+                    i--;
+                    break;
+                }
+            }
+            i++;
+        }
+    }
+
+    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
+        if (returnsNameInThisLocale) {
+            return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+        } else {
+            return toTitleCase(locale.getDisplayName(), locale);
+        }
+    }
+
+    public static String getDisplayLanguage(Locale locale) {
+        return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+    }
+
+    public static String getMiddleDisplayLanguage(Locale locale) {
+        return toTitleCase((LocaleUtils.constructLocaleFromString(
+                locale.getLanguage()).getDisplayLanguage(locale)), locale);
+    }
+
+    public static String getShortDisplayLanguage(Locale locale) {
+        return toTitleCase(locale.getLanguage(), locale);
+    }
+
+    public static String toTitleCase(String s, Locale locale) {
+        if (s.length() <= 1) {
+            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
+            return s;
+        }
+        // TODO: fix the bugs below
+        // - This does not work for Greek, because it returns upper case instead of title case.
+        // - It does not work for Serbian, because it fails to account for the "lj" character,
+        // which should be "Lj" in title case and "LJ" in upper case.
+        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
+        // are two different characters but both should be capitalized as "IJ" as if they were
+        // a single letter.
+        // - It also does not work with unicode surrogate code points.
+        return s.toUpperCase(locale).charAt(0) + s.substring(1);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 917521c..66c13bd 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -36,10 +36,16 @@
     }
 
     public static String getFullDisplayName(Locale locale) {
-        String localeCode = locale.toString();
+        final String localeCode = locale.toString();
         for (int index = 0; index < sExceptionKeys.length; index++) {
-            if (sExceptionKeys[index].equals(localeCode))
-                return sExceptionValues[index];
+            if (sExceptionKeys[index].equals(localeCode)) {
+                final String value = sExceptionValues[index];
+                if (value.indexOf("%s") >= 0) {
+                    final String languageName = locale.getDisplayLanguage(locale);
+                    return String.format(value, languageName);
+                }
+                return value;
+            }
         }
         return locale.getDisplayName(locale);
     }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 8a48620..e353644 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -29,16 +29,13 @@
 import android.os.IBinder;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
-import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.LatinKeyboard;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -47,37 +44,35 @@
     private static boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
 
-    private static final char LOCALE_SEPARATER = '_';
-    private static final String KEYBOARD_MODE = "keyboard";
-    private static final String VOICE_MODE = "voice";
+    public static final String KEYBOARD_MODE = "keyboard";
+    private static final char LOCALE_SEPARATOR = '_';
     private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
             "requireNetworkConnectivity";
 
     private final TextUtils.SimpleStringSplitter mLocaleSplitter =
-            new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
+            new TextUtils.SimpleStringSplitter(LOCALE_SEPARATOR);
 
     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
     private /* final */ LatinIME mService;
     private /* final */ InputMethodManagerCompatWrapper mImm;
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
-    private final ArrayList<InputMethodSubtypeCompatWrapper>
-            mEnabledKeyboardSubtypesOfCurrentInputMethod =
-                    new ArrayList<InputMethodSubtypeCompatWrapper>();
+    private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
+            new ArrayList<InputMethodSubtype>();
     private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
 
     /*-----------------------------------------------------------*/
     // Variants which should be changed only by reload functions.
     private boolean mNeedsToDisplayLanguage;
     private boolean mIsSystemLanguageSameAsInputLanguage;
-    private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
-    private InputMethodSubtypeCompatWrapper mShortcutSubtype;
-    private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
-    private InputMethodSubtypeCompatWrapper mCurrentSubtype;
+    private InputMethodInfo mShortcutInputMethodInfo;
+    private InputMethodSubtype mShortcutSubtype;
+    private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
+    // Note: This variable is always non-null after {@link #initialize(LatinIME)}.
+    private InputMethodSubtype mCurrentSubtype;
     private Locale mSystemLocale;
     private Locale mInputLocale;
     private String mInputLocaleStr;
-    private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
     /*-----------------------------------------------------------*/
 
     private boolean mIsNetworkConnected;
@@ -107,9 +102,8 @@
         mSystemLocale = null;
         mInputLocale = null;
         mInputLocaleStr = null;
-        mCurrentSubtype = null;
+        mCurrentSubtype = mImm.getCurrentInputMethodSubtype();
         mAllEnabledSubtypesOfCurrentInputMethod = null;
-        mVoiceInputWrapper = null;
 
         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
@@ -138,7 +132,7 @@
                 null, true);
         mEnabledLanguagesOfCurrentInputMethod.clear();
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
-        for (InputMethodSubtypeCompatWrapper ims : mAllEnabledSubtypesOfCurrentInputMethod) {
+        for (InputMethodSubtype ims : mAllEnabledSubtypesOfCurrentInputMethod) {
             final String locale = getSubtypeLocale(ims);
             final String mode = ims.getMode();
             mLocaleSplitter.setString(locale);
@@ -172,12 +166,12 @@
                             + ", " + mShortcutSubtype.getMode())));
         }
         // TODO: Update an icon for shortcut IME
-        final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
+        final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
                 mImm.getShortcutInputMethodsAndSubtypes();
         mShortcutInputMethodInfo = null;
         mShortcutSubtype = null;
-        for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
-            List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
+        for (InputMethodInfo imi : shortcuts.keySet()) {
+            List<InputMethodSubtype> subtypes = shortcuts.get(imi);
             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
             // appropriate.
             mShortcutInputMethodInfo = imi;
@@ -195,27 +189,17 @@
         }
     }
 
-    private static String getSubtypeLocale(InputMethodSubtypeCompatWrapper subtype) {
+    private static String getSubtypeLocale(InputMethodSubtype subtype) {
         final String keyboardLocale = subtype.getExtraValueOf(
                 LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE);
         return keyboardLocale != null ? keyboardLocale : subtype.getLocale();
     }
 
     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
-    public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
-        final String newLocale;
-        final String newMode;
+    public void updateSubtype(InputMethodSubtype newSubtype) {
+        final String newLocale = getSubtypeLocale(newSubtype);
+        final String newMode = newSubtype.getMode();
         final String oldMode = getCurrentSubtypeMode();
-        if (newSubtype == null) {
-            // Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
-            // fallback to the default locale.
-            Log.w(TAG, "Couldn't get the current subtype.");
-            newLocale = "en_US";
-            newMode = KEYBOARD_MODE;
-        } else {
-            newLocale = getSubtypeLocale(newSubtype);
-            newMode = newSubtype.getMode();
-        }
         if (DBG) {
             Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode
                     + ", from: " + mInputLocaleStr + ", " + oldMode);
@@ -235,34 +219,12 @@
         }
         mCurrentSubtype = newSubtype;
 
-        // If the old mode is voice input, we need to reset or cancel its status.
-        // We cancel its status when we change mode, while we reset otherwise.
         if (isKeyboardMode()) {
-            if (modeChanged) {
-                if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
-                    mVoiceInputWrapper.cancel();
-                }
-            }
             if (modeChanged || languageChanged) {
                 updateShortcutIME();
                 mService.onRefreshKeyboard();
             }
-        } else if (isVoiceMode() && mVoiceInputWrapper != null) {
-            if (VOICE_MODE.equals(oldMode)) {
-                mVoiceInputWrapper.reset();
-            }
-            // If needsToShowWarningDialog is true, voice input need to show warning before
-            // show recognition view.
-            if (languageChanged || modeChanged
-                    || VoiceProxy.getInstance().needsToShowWarningDialog()) {
-                triggerVoiceIME();
-            }
         } else {
-            if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
-                // We need to reset the voice input to release the resources and to reset its status
-                // as it is not the current input mode.
-                mVoiceInputWrapper.reset();
-            }
             final String packageName = mService.getPackageName();
             int version = -1;
             try {
@@ -271,7 +233,7 @@
             } catch (NameNotFoundException e) {
             }
             Log.w(TAG, "Unknown subtype mode: " + newMode + "," + version + ", " + packageName
-                    + ", " + mVoiceInputWrapper + ". IME is already changed to other IME.");
+                    + ". IME is already changed to other IME.");
             if (newSubtype != null) {
                 Log.w(TAG, "Subtype mode:" + newSubtype.getMode());
                 Log.w(TAG, "Subtype locale:" + newSubtype.getLocale());
@@ -312,12 +274,10 @@
         }
 
         final String imiId = mShortcutInputMethodInfo.getId();
-        final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
-        switchToTargetIME(imiId, subtype);
+        switchToTargetIME(imiId, mShortcutSubtype);
     }
 
-    private void switchToTargetIME(
-            final String imiId, final InputMethodSubtypeCompatWrapper subtype) {
+    private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
         final IBinder token = mService.getWindow().getWindow().getAttributes().token;
         if (token == null) {
             return;
@@ -328,17 +288,6 @@
                 mImm.setInputMethodAndSubtype(token, imiId, subtype);
                 return null;
             }
-
-            @Override
-            protected void onPostExecute(Void result) {
-                // Calls in this method need to be done in the same thread as the thread which
-                // called switchToShortcutIME().
-
-                // Notify an event that the current subtype was changed. This event will be
-                // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
-                // when the API level is 10 or previous.
-                mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
-            }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
@@ -346,8 +295,7 @@
         return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
     }
 
-    private Drawable getSubtypeIcon(
-            InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
+    private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
         final PackageManager pm = mService.getPackageManager();
         if (imi != null) {
             final String imiPackageName = imi.getPackageName();
@@ -388,15 +336,9 @@
         if (mShortcutSubtype == null) {
             return true;
         }
-        // For compatibility, if the shortcut subtype is dummy, we assume the shortcut IME
-        // (built-in voice dummy subtype) is available.
-        if (!mShortcutSubtype.hasOriginalObject()) {
-            return true;
-        }
         final boolean allowsImplicitlySelectedSubtypes = true;
-        for (final InputMethodSubtypeCompatWrapper enabledSubtype :
-                mImm.getEnabledInputMethodSubtypeList(
-                        mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+        for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
+                mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
             if (enabledSubtype.equals(mShortcutSubtype)) {
                 return true;
             }
@@ -421,11 +363,7 @@
                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
         mIsNetworkConnected = !noConnection;
 
-        final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
-        final LatinKeyboard keyboard = switcher.getLatinKeyboard();
-        if (keyboard != null) {
-            keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView());
-        }
+        KeyboardSwitcher.getInstance().onNetworkStateChanged();
     }
 
     //////////////////////////////////
@@ -482,42 +420,8 @@
         return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
     }
 
-
-    ///////////////////////////
-    // Voice Input functions //
-    ///////////////////////////
-
-    public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
-        if (mVoiceInputWrapper == null && vi != null) {
-            mVoiceInputWrapper = vi;
-            if (isVoiceMode()) {
-                if (DBG) {
-                    Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
-                }
-                triggerVoiceIME();
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean isVoiceMode() {
-        return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
-    }
-
-    public boolean isDummyVoiceMode() {
-        return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
-                && VOICE_MODE.equals(getCurrentSubtypeMode());
-    }
-
-    private void triggerVoiceIME() {
-        if (!mService.isInputViewShown()) return;
-        VoiceProxy.getInstance().startListening(false,
-                KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken());
-    }
-
     public String getInputLanguageName() {
-        return Utils.getDisplayLanguage(getInputLocale());
+        return StringUtils.getDisplayLanguage(getInputLocale());
     }
 
     /////////////////////////////
@@ -526,34 +430,20 @@
 
     public String getCurrentSubtypeExtraValue() {
         // If null, return what an empty ExtraValue would return : the empty string.
-        return null != mCurrentSubtype ? mCurrentSubtype.getExtraValue() : "";
+        return mCurrentSubtype.getExtraValue();
     }
 
     public boolean currentSubtypeContainsExtraValueKey(String key) {
         // If null, return what an empty ExtraValue would return : false.
-        return null != mCurrentSubtype ? mCurrentSubtype.containsExtraValueKey(key) : false;
+        return mCurrentSubtype.containsExtraValueKey(key);
     }
 
     public String getCurrentSubtypeExtraValueOf(String key) {
         // If null, return what an empty ExtraValue would return : null.
-        return null != mCurrentSubtype ? mCurrentSubtype.getExtraValueOf(key) : null;
+        return mCurrentSubtype.getExtraValueOf(key);
     }
 
     public String getCurrentSubtypeMode() {
-        return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE;
-    }
-
-
-    public static boolean isVoiceSupported(Context context, String locale) {
-        // Get the current list of supported locales and check the current locale against that
-        // list. We cache this value so as not to check it every time the user starts a voice
-        // input. Because this method is called by onStartInputView, this should mean that as
-        // long as the locale doesn't change while the user is keeping the IME open, the
-        // value should never be stale.
-        String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
-                context.getContentResolver());
-        List<String> voiceInputSupportedLocales = Arrays.asList(
-                supportedLocalesString.split("\\s+"));
-        return voiceInputSupportedLocales.contains(locale);
+        return mCurrentSubtype.getMode();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeUtils.java b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
new file mode 100644
index 0000000..2c5d582
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SubtypeUtils {
+    private SubtypeUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    // TODO: Cache my InputMethodInfo and/or InputMethodSubtype list.
+    public static boolean checkIfSubtypeBelongsToThisIme(Context context, InputMethodSubtype ims) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        final InputMethodInfo myImi = getInputMethodInfo(context.getPackageName());
+        final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(myImi, true);
+        for (final InputMethodSubtype subtype : subtypes) {
+            if (subtype.equals(ims)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean hasMultipleEnabledIMEsOrSubtypes(
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
+    }
+
+    public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodInfo myImi = getInputMethodInfo(context.getPackageName());
+        final List<InputMethodInfo> imiList = Collections.singletonList(myImi);
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
+    }
+
+    private static boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
+            List<InputMethodInfo> imiList) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        // Number of the filtered IMEs
+        int filteredImisCount = 0;
+
+        for (InputMethodInfo imi : imiList) {
+            // We can return true immediately after we find two or more filtered IMEs.
+            if (filteredImisCount > 1) return true;
+            final List<InputMethodSubtype> subtypes =
+                    imm.getEnabledInputMethodSubtypeList(imi, true);
+            // IMEs that have no subtypes should be counted.
+            if (subtypes.isEmpty()) {
+                ++filteredImisCount;
+                continue;
+            }
+
+            int auxCount = 0;
+            for (InputMethodSubtype subtype : subtypes) {
+                if (subtype.isAuxiliary()) {
+                    ++auxCount;
+                }
+            }
+            final int nonAuxCount = subtypes.size() - auxCount;
+
+            // IMEs that have one or more non-auxiliary subtypes should be counted.
+            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+            // subtypes should be counted as well.
+            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+                ++filteredImisCount;
+                continue;
+            }
+        }
+
+        if (filteredImisCount > 1) {
+            return true;
+        }
+        final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(null, true);
+        int keyboardCount = 0;
+        // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
+        // both explicitly and implicitly enabled input method subtype.
+        // (The current IME should be LatinIME.)
+        for (InputMethodSubtype subtype : subtypes) {
+            if (SubtypeSwitcher.KEYBOARD_MODE.equals(subtype.getMode())) {
+                ++keyboardCount;
+            }
+        }
+        return keyboardCount > 1;
+    }
+
+    public static String getInputMethodId(String packageName) {
+        return getInputMethodInfo(packageName).getId();
+    }
+
+    public static InputMethodInfo getInputMethodInfo(String packageName) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) {
+            throw new RuntimeException("Input method manager not found");
+        }
+
+        for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+            if (imi.getPackageName().equals(packageName))
+                return imi;
+        }
+        throw new RuntimeException("Can not find input method id for " + packageName);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index caa5aac..b31f301 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -20,31 +20,28 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
  * characters. This includes corrections and completions.
  */
 public class Suggest implements Dictionary.WordCallback {
-
     public static final String TAG = Suggest.class.getSimpleName();
 
     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;
+    public static final int CORRECTION_FULL = 1;
+    public static final int CORRECTION_FULL_BIGRAM = 2;
 
     /**
      * Words that appear in both bigram and unigram data gets multiplier ranging from
@@ -64,9 +61,8 @@
     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_USER_UNIGRAM = 3;
+    public static final int DIC_USER_HISTORY = 3;
     public static final int DIC_CONTACTS = 4;
-    public static final int DIC_USER_BIGRAM = 5;
     public static final int DIC_WHITELIST = 6;
     // If you add a type of dictionary, increment DIC_TYPE_LAST_ID
     // TODO: this value seems unused. Remove it?
@@ -75,39 +71,38 @@
     public static final String DICT_KEY_CONTACTS = "contacts";
     // User dictionary, the system-managed one.
     public static final String DICT_KEY_USER = "user";
-    // User unigram dictionary, internal to LatinIME
-    public static final String DICT_KEY_USER_UNIGRAM = "user_unigram";
-    // User bigram dictionary, internal to LatinIME
-    public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
+    // User history dictionary for the unigram map, internal to LatinIME
+    public static final String DICT_KEY_USER_HISTORY_UNIGRAM = "history_unigram";
+    // User history dictionary for the bigram map, internal to LatinIME
+    public static final String DICT_KEY_USER_HISTORY_BIGRAM = "history_bigram";
     public static final String DICT_KEY_WHITELIST ="whitelist";
 
     private static final boolean DBG = LatinImeLogger.sDBG;
 
-    private AutoCorrection mAutoCorrection;
-
     private Dictionary mMainDict;
     private ContactsDictionary mContactsDict;
     private WhitelistDictionary mWhiteListDictionary;
-    private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
-    private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
+    private final HashMap<String, Dictionary> mUnigramDictionaries =
+            new HashMap<String, Dictionary>();
+    private final HashMap<String, Dictionary> mBigramDictionaries =
+            new HashMap<String, Dictionary>();
 
     private int mPrefMaxSuggestions = 18;
 
     private static final int PREF_MAX_BIGRAMS = 60;
 
     private double mAutoCorrectionThreshold;
-    private int[] mScores = new int[mPrefMaxSuggestions];
-    private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
 
-    private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
-    ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
-    private CharSequence mTypedWord;
+    private ArrayList<SuggestedWordInfo> mSuggestions = new ArrayList<SuggestedWordInfo>();
+    private ArrayList<SuggestedWordInfo> mBigramSuggestions = new ArrayList<SuggestedWordInfo>();
+    private CharSequence mConsideredWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
     private boolean mIsFirstCharCapitalized;
     private boolean mIsAllUpperCase;
+    private int mTrailingSingleQuotesCount;
 
-    private int mCorrectionMode = CORRECTION_BASIC;
+    private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
 
     public Suggest(final Context context, final int dictionaryResId, final Locale locale) {
         initAsynchronously(context, dictionaryResId, locale);
@@ -116,15 +111,13 @@
     /* package for test */ Suggest(final Context context, final File dictionary,
             final long startOffset, final long length, final Flag[] flagArray,
             final Locale locale) {
-        initSynchronously(null, DictionaryFactory.createDictionaryForTest(context, dictionary,
+        initSynchronously(context, DictionaryFactory.createDictionaryForTest(context, dictionary,
                 startOffset, length, flagArray), locale);
     }
 
     private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
         mWhiteListDictionary = new WhitelistDictionary(context, locale);
         addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
-        mAutoCorrection = new AutoCorrection();
-        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
     }
 
     private void initAsynchronously(final Context context, final int dictionaryResId,
@@ -144,7 +137,7 @@
         initWhitelistAndAutocorrectAndPool(context, locale);
     }
 
-    private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
+    private static void addOrReplaceDictionary(HashMap<String, Dictionary> dictionaries, String key,
             Dictionary dict) {
         final Dictionary oldDict = (dict == null)
                 ? dictionaries.remove(key)
@@ -169,14 +162,6 @@
         }.start();
     }
 
-    public int getCorrectionMode() {
-        return mCorrectionMode;
-    }
-
-    public void setCorrectionMode(int mode) {
-        mCorrectionMode = mode;
-    }
-
     // The main dictionary could have been loaded asynchronously.  Don't cache the return value
     // of this method.
     public boolean hasMainDictionary() {
@@ -187,11 +172,11 @@
         return mContactsDict;
     }
 
-    public Map<String, Dictionary> getUnigramDictionaries() {
+    public HashMap<String, Dictionary> getUnigramDictionaries() {
         return mUnigramDictionaries;
     }
 
-    public int getApproxMaxWordLength() {
+    public static int getApproxMaxWordLength() {
         return APPROX_MAX_WORD_LENGTH;
     }
 
@@ -214,56 +199,22 @@
         addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
     }
 
-    public void setUserUnigramDictionary(Dictionary userUnigramDictionary) {
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_UNIGRAM, userUnigramDictionary);
-    }
-
-    public void setUserBigramDictionary(Dictionary userBigramDictionary) {
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_BIGRAM, userBigramDictionary);
+    public void setUserHistoryDictionary(Dictionary userHistoryDictionary) {
+        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM,
+                userHistoryDictionary);
+        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM,
+                userHistoryDictionary);
     }
 
     public void setAutoCorrectionThreshold(double threshold) {
         mAutoCorrectionThreshold = threshold;
     }
 
-    public boolean isAggressiveAutoCorrectionMode() {
-        return (mAutoCorrectionThreshold == 0);
-    }
-
-    /**
-     * Number of suggestions to generate from the input key sequence. This has
-     * to be a number between 1 and 100 (inclusive).
-     * @param maxSuggestions
-     * @throws IllegalArgumentException if the number is out of range
-     */
-    public void setMaxSuggestions(int maxSuggestions) {
-        if (maxSuggestions < 1 || maxSuggestions > 100) {
-            throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
-        }
-        mPrefMaxSuggestions = maxSuggestions;
-        mScores = new int[mPrefMaxSuggestions];
-        mBigramScores = new int[PREF_MAX_BIGRAMS];
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
-        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
-    }
-
-    /**
-     * Returns a object which represents suggested words that match the list of character codes
-     * passed in. This object contents will be overwritten the next time this function is called.
-     * @param wordComposer contains what is currently being typed
-     * @param prevWordForBigram previous word (used only for bigram)
-     * @return suggested words object.
-     */
-    public SuggestedWords getSuggestions(final WordComposer wordComposer,
-            final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) {
-        return getSuggestedWordBuilder(wordComposer, prevWordForBigram,
-                proximityInfo).build();
-    }
-
-    private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
+    private static CharSequence capitalizeWord(final boolean all, final boolean first,
+            final CharSequence word) {
         if (TextUtils.isEmpty(word) || !(all || first)) return word;
         final int wordLength = word.length();
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         // TODO: Must pay attention to locale when changing case.
         if (all) {
             sb.append(word.toString().toUpperCase());
@@ -276,42 +227,68 @@
         return sb;
     }
 
-    protected void addBigramToSuggestions(CharSequence bigram) {
-        // TODO: Try to be a little more shrewd with resource allocation.
-        // At the moment we copy this object because the StringBuilders are pooled (see
-        // StringBuilderPool.java) and when we are finished using mSuggestions and
-        // mBigramSuggestions we will take everything from both and insert them back in the
-        // pool, so we can't allow the same object to be in both lists at the same time.
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
-        sb.append(bigram);
-        mSuggestions.add(sb);
+    protected void addBigramToSuggestions(SuggestedWordInfo bigram) {
+        mSuggestions.add(bigram);
+    }
+
+    private static final WordComposer sEmptyWordComposer = new WordComposer();
+    public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) {
+        LatinImeLogger.onStartSuggestion(prevWordForBigram);
+        mIsFirstCharCapitalized = false;
+        mIsAllUpperCase = false;
+        mTrailingSingleQuotesCount = 0;
+        mSuggestions = new ArrayList<SuggestedWordInfo>(mPrefMaxSuggestions);
+
+        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
+        LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
+        mConsideredWord = "";
+
+        mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
+
+        CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
+        if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
+            prevWordForBigram = lowerPrevWord;
+        }
+        for (final Dictionary dictionary : mBigramDictionaries.values()) {
+            dictionary.getBigrams(sEmptyWordComposer, prevWordForBigram, this);
+        }
+        // Nothing entered: return all bigrams for the previous word
+        int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
+        for (int i = 0; i < insertCount; ++i) {
+            addBigramToSuggestions(mBigramSuggestions.get(i));
+        }
+
+        SuggestedWordInfo.removeDups(mSuggestions);
+
+        return new SuggestedWords(mSuggestions,
+                false /* typedWordValid */,
+                false /* hasAutoCorrectionCandidate */,
+                false /* allowsToBeAutoCorrected */,
+                false /* isPunctuationSuggestions */,
+                false /* isObsoleteSuggestions */);
     }
 
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
-    public SuggestedWords.Builder getSuggestedWordBuilder(
+    public SuggestedWords getSuggestedWords(
             final WordComposer wordComposer, CharSequence prevWordForBigram,
-            final ProximityInfo proximityInfo) {
+            final ProximityInfo proximityInfo, final int correctionMode) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mAutoCorrection.init();
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
-        Arrays.fill(mScores, 0);
+        mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
+        mSuggestions = new ArrayList<SuggestedWordInfo>(mPrefMaxSuggestions);
 
-        // Save a lowercase version of the original word
-        String typedWord = wordComposer.getTypedWord();
-        if (typedWord != null) {
-            // Treating USER_TYPED as UNIGRAM suggestion for logging now.
-            LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
-                    Dictionary.DataType.UNIGRAM);
-        }
-        mTypedWord = typedWord;
+        final String typedWord = wordComposer.getTypedWord();
+        final String consideredWord = mTrailingSingleQuotesCount > 0
+                ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+                : typedWord;
+        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
+        LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
+        mConsideredWord = consideredWord;
 
-        if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
-                || mCorrectionMode == CORRECTION_BASIC)) {
+        if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
             // At first character typed, search only the bigrams
-            Arrays.fill(mBigramScores, 0);
-            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+            mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
 
             if (!TextUtils.isEmpty(prevWordForBigram)) {
                 CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@@ -321,7 +298,7 @@
                 for (final Dictionary dictionary : mBigramDictionaries.values()) {
                     dictionary.getBigrams(wordComposer, prevWordForBigram, this);
                 }
-                if (TextUtils.isEmpty(typedWord)) {
+                if (TextUtils.isEmpty(consideredWord)) {
                     // Nothing entered: return all bigrams for the previous word
                     int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
                     for (int i = 0; i < insertCount; ++i) {
@@ -329,15 +306,16 @@
                     }
                 } else {
                     // Word entered: return only bigrams that match the first char of the typed word
-                    @SuppressWarnings("null")
-                    final char currentChar = typedWord.charAt(0);
+                    final char currentChar = consideredWord.charAt(0);
                     // TODO: Must pay attention to locale when changing case.
+                    // TODO: Use codepoint instead of char
                     final char currentCharUpper = Character.toUpperCase(currentChar);
                     int count = 0;
                     final int bigramSuggestionSize = mBigramSuggestions.size();
                     for (int i = 0; i < bigramSuggestionSize; i++) {
-                        final CharSequence bigramSuggestion = mBigramSuggestions.get(i);
-                        final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0);
+                        final SuggestedWordInfo bigramSuggestion = mBigramSuggestions.get(i);
+                        final char bigramSuggestionFirstChar =
+                                (char)bigramSuggestion.codePointAt(0);
                         if (bigramSuggestionFirstChar == currentChar
                                 || bigramSuggestionFirstChar == currentCharUpper) {
                             addBigramToSuggestions(bigramSuggestion);
@@ -348,104 +326,157 @@
             }
 
         } else if (wordComposer.size() > 1) {
+            final WordComposer wordComposerForLookup;
+            if (mTrailingSingleQuotesCount > 0) {
+                wordComposerForLookup = new WordComposer(wordComposer);
+                for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+                    wordComposerForLookup.deleteLast();
+                }
+            } else {
+                wordComposerForLookup = wordComposer;
+            }
             // At second character typed, search the unigrams (scores being affected by bigrams)
             for (final String key : mUnigramDictionaries.keySet()) {
                 // Skip UserUnigramDictionary and WhitelistDictionary to lookup
-                if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
+                if (key.equals(DICT_KEY_USER_HISTORY_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
                     continue;
                 final Dictionary dictionary = mUnigramDictionaries.get(key);
-                dictionary.getWords(wordComposer, this, proximityInfo);
+                dictionary.getWords(wordComposerForLookup, this, proximityInfo);
             }
         }
-        final String typedWordString = typedWord == null ? null : typedWord.toString();
 
-        CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
-                mWhiteListDictionary.getWhitelistedWord(typedWordString));
+        final CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase,
+                mIsFirstCharCapitalized, mWhiteListDictionary.getWhitelistedWord(consideredWord));
 
-        mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
-                mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
-                whitelistedWord);
+        final boolean hasAutoCorrection;
+        if (CORRECTION_FULL == correctionMode || CORRECTION_FULL_BIGRAM == correctionMode) {
+            final CharSequence autoCorrection =
+                    AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer,
+                            mSuggestions, consideredWord, mAutoCorrectionThreshold,
+                            whitelistedWord);
+            hasAutoCorrection = (null != autoCorrection);
+        } else {
+            hasAutoCorrection = false;
+        }
 
         if (whitelistedWord != null) {
-            mSuggestions.add(0, whitelistedWord);
-        }
-
-        if (typedWord != null) {
-            mSuggestions.add(0, typedWordString);
-        }
-        Utils.removeDupes(mSuggestions);
-
-        if (DBG) {
-            double normalizedScore = mAutoCorrection.getNormalizedScore();
-            ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
-                    new ArrayList<SuggestedWords.SuggestedWordInfo>();
-            scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
-            for (int i = 0; i < mScores.length; ++i) {
-                if (normalizedScore > 0) {
-                    final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
-                            normalizedScore);
-                    scoreInfoList.add(
-                            new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
-                    normalizedScore = 0.0;
-                } else {
-                    final String score = Integer.toString(mScores[i]);
-                    scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
+            if (mTrailingSingleQuotesCount > 0) {
+                final StringBuilder sb = new StringBuilder(whitelistedWord);
+                for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+                    sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
                 }
+                mSuggestions.add(0, new SuggestedWordInfo(
+                        sb.toString(), SuggestedWordInfo.MAX_SCORE));
+            } else {
+                mSuggestions.add(0, new SuggestedWordInfo(
+                        whitelistedWord, SuggestedWordInfo.MAX_SCORE));
             }
-            for (int i = mScores.length; i < mSuggestions.size(); ++i) {
-                scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
-            }
-            return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
         }
-        return new SuggestedWords.Builder().addWords(mSuggestions, null);
+
+        mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE));
+        SuggestedWordInfo.removeDups(mSuggestions);
+
+        final ArrayList<SuggestedWordInfo> suggestionsList;
+        if (DBG) {
+            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions);
+        } else {
+            suggestionsList = mSuggestions;
+        }
+
+        // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
+        // but still autocorrected from - in the case the whitelist only capitalizes the word.
+        // The whitelist should be case-insensitive, so it's not possible to be consistent with
+        // a boolean flag. Right now this is handled with a slight hack in
+        // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
+        final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
+                getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized());
+
+        boolean autoCorrectionAvailable = hasAutoCorrection;
+        if (correctionMode == CORRECTION_FULL || correctionMode == CORRECTION_FULL_BIGRAM) {
+            autoCorrectionAvailable |= !allowsToBeAutoCorrected;
+        }
+        // Don't auto-correct words with multiple capital letter
+        autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
+        if (allowsToBeAutoCorrected && suggestionsList.size() > 1 && mAutoCorrectionThreshold > 0
+                && Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord,
+                        suggestionsList.get(1).mWord)) {
+            autoCorrectionAvailable = false;
+        }
+        return new SuggestedWords(suggestionsList,
+                !allowsToBeAutoCorrected /* typedWordValid */,
+                autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
+                allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
+                false /* isPunctuationSuggestions */,
+                false /* isObsoleteSuggestions */);
     }
 
-    public boolean hasAutoCorrection() {
-        return mAutoCorrection.hasAutoCorrection();
+    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
+            final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
+        final SuggestedWordInfo typedWordInfo = suggestions.get(0);
+        typedWordInfo.setDebugString("+");
+        double normalizedScore = BinaryDictionary.calcNormalizedScore(
+                typedWord, typedWordInfo.toString(), typedWordInfo.mScore);
+        final int suggestionsSize = suggestions.size();
+        final ArrayList<SuggestedWordInfo> suggestionsList =
+                new ArrayList<SuggestedWordInfo>(suggestionsSize);
+        suggestionsList.add(typedWordInfo);
+        // Note: i here is the index in mScores[], but the index in mSuggestions is one more
+        // than i because we added the typed word to mSuggestions without touching mScores.
+        for (int i = 0; i < suggestionsSize - 1; ++i) {
+            final SuggestedWordInfo cur = suggestions.get(i + 1);
+            final String scoreInfoString;
+            if (normalizedScore > 0) {
+                scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore);
+                normalizedScore = 0.0;
+            } else {
+                scoreInfoString = Integer.toString(cur.mScore);
+            }
+            cur.setDebugString(scoreInfoString);
+            suggestionsList.add(cur);
+        }
+        return suggestionsList;
     }
 
+    // TODO: Use codepoint instead of char
     @Override
     public boolean addWord(final char[] word, final int offset, final int length, int score,
-            final int dicTypeId, final Dictionary.DataType dataType) {
-        Dictionary.DataType dataTypeForLog = dataType;
-        final ArrayList<CharSequence> suggestions;
-        final int[] sortedScores;
+            final int dicTypeId, final int dataType) {
+        int dataTypeForLog = dataType;
+        final ArrayList<SuggestedWordInfo> suggestions;
         final int prefMaxSuggestions;
-        if(dataType == Dictionary.DataType.BIGRAM) {
+        if (dataType == Dictionary.BIGRAM) {
             suggestions = mBigramSuggestions;
-            sortedScores = mBigramScores;
             prefMaxSuggestions = PREF_MAX_BIGRAMS;
         } else {
             suggestions = mSuggestions;
-            sortedScores = mScores;
             prefMaxSuggestions = mPrefMaxSuggestions;
         }
 
         int pos = 0;
 
         // Check if it's the same word, only caps are different
-        if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
+        if (StringUtils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
             // TODO: remove this surrounding if clause and move this logic to
             // getSuggestedWordBuilder.
             if (suggestions.size() > 0) {
-                final String currentHighestWord = suggestions.get(0).toString();
+                final SuggestedWordInfo currentHighestWord = suggestions.get(0);
                 // If the current highest word is also equal to typed word, we need to compare
                 // frequency to determine the insertion position. This does not ensure strictly
                 // correct ordering, but ensures the top score is on top which is enough for
                 // removing duplicates correctly.
-                if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
-                        && score <= sortedScores[0]) {
+                if (StringUtils.equalsIgnoreCase(currentHighestWord.mWord, word, offset, length)
+                        && score <= currentHighestWord.mScore) {
                     pos = 1;
                 }
             }
         } else {
-            if (dataType == Dictionary.DataType.UNIGRAM) {
+            if (dataType == Dictionary.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;
+                    dataTypeForLog = Dictionary.BIGRAM;
                     // turn freq from bigram into multiplier specified above
-                    double multiplier = (((double) mBigramScores[bigramSuggestion])
+                    double multiplier = (((double) mBigramSuggestions.get(bigramSuggestion).mScore)
                             / MAXIMUM_BIGRAM_FREQUENCY)
                             * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
                             + BIGRAM_MULTIPLIER_MIN;
@@ -459,10 +490,12 @@
             }
 
             // Check the last one's score and bail
-            if (sortedScores[prefMaxSuggestions - 1] >= score) return true;
-            while (pos < prefMaxSuggestions) {
-                if (sortedScores[pos] < score
-                        || (sortedScores[pos] == score && length < suggestions.get(pos).length())) {
+            if (suggestions.size() >= prefMaxSuggestions
+                    && suggestions.get(prefMaxSuggestions - 1).mScore >= score) return true;
+            while (pos < suggestions.size()) {
+                final int curScore = suggestions.get(pos).mScore;
+                if (curScore < score
+                        || (curScore == score && length < suggestions.get(pos).codePointCount())) {
                     break;
                 }
                 pos++;
@@ -472,9 +505,7 @@
             return true;
         }
 
-        System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
-        sortedScores[pos] = score;
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         // TODO: Must pay attention to locale when changing case.
         if (mIsAllUpperCase) {
             sb.append(new String(word, offset, length).toUpperCase());
@@ -486,57 +517,41 @@
         } else {
             sb.append(word, offset, length);
         }
-        suggestions.add(pos, sb);
+        for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+            sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+        }
+        suggestions.add(pos, new SuggestedWordInfo(sb, score));
         if (suggestions.size() > prefMaxSuggestions) {
-            final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
-            if (garbage instanceof StringBuilder) {
-                StringBuilderPool.recycle((StringBuilder)garbage);
-            }
+            suggestions.remove(prefMaxSuggestions);
         } else {
             LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
         }
         return true;
     }
 
+    // TODO: Use codepoint instead of char
     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) {
+        for (int i = 0; i < bigramSuggestSize; i++) {
+            if (mBigramSuggestions.get(i).codePointCount() == length) {
                 boolean chk = true;
-                for(int j = 0; j < length; j++) {
-                    if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
+                for (int j = 0; j < length; j++) {
+                    if (mBigramSuggestions.get(i).codePointAt(j) != word[offset+j]) {
                         chk = false;
                         break;
                     }
                 }
-                if(chk) return i;
+                if (chk) return i;
             }
         }
 
         return -1;
     }
 
-    private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
-        int poolSize = StringBuilderPool.getSize();
-        int garbageSize = suggestions.size();
-        while (poolSize < prefMaxSuggestions && garbageSize > 0) {
-            final CharSequence garbage = suggestions.get(garbageSize - 1);
-            if (garbage instanceof StringBuilder) {
-                StringBuilderPool.recycle((StringBuilder)garbage);
-                poolSize++;
-            }
-            garbageSize--;
-        }
-        if (poolSize == prefMaxSuggestions + 1) {
-            Log.w("Suggest", "String pool got too big: " + poolSize);
-        }
-        suggestions.clear();
-    }
-
     public void close() {
-        final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
+        final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
         dictionaries.addAll(mUnigramDictionaries.values());
         dictionaries.addAll(mBigramDictionaries.values());
         for (final Dictionary dictionary : dictionaries) {
@@ -544,4 +559,37 @@
         }
         mMainDict = null;
     }
+
+    // TODO: Resolve the inconsistencies between the native auto correction algorithms and
+    // this safety net
+    public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
+            final CharSequence suggestion) {
+        // Safety net for auto correction.
+        // Actually if we hit this safety net, it's a bug.
+        // If user selected aggressive auto correction mode, there is no need to use the safety
+        // net.
+        // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
+        // we should not use net because relatively edit distance can be big.
+        final int typedWordLength = typedWord.length();
+        if (typedWordLength < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) {
+            return false;
+        }
+        final int maxEditDistanceOfNativeDictionary =
+                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
+        final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString());
+        if (DBG) {
+            Log.d(TAG, "Autocorrected edit distance = " + distance
+                    + ", " + maxEditDistanceOfNativeDictionary);
+        }
+        if (distance > maxEditDistanceOfNativeDictionary) {
+            if (DBG) {
+                Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
+                Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
+                        + "Turning off auto-correction.");
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index ed6359c..0c0ce18 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -20,211 +20,160 @@
 import android.view.inputmethod.CompletionInfo;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.HashSet;
-import java.util.List;
 
 public class SuggestedWords {
-    public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, false, null);
+    public static final SuggestedWords EMPTY = new SuggestedWords(
+            new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
 
-    public final List<CharSequence> mWords;
     public final boolean mTypedWordValid;
     public final boolean mHasAutoCorrectionCandidate;
     public final boolean mIsPunctuationSuggestions;
-    private final List<SuggestedWordInfo> mSuggestedWordInfoList;
-    private boolean mShouldBlockAutoCorrection;
+    public final boolean mAllowsToBeAutoCorrected;
+    public final boolean mIsObsoleteSuggestions;
+    private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList;
 
-    private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
-            boolean hasAutoCorrectionCandidate, boolean isPunctuationSuggestions,
-            List<SuggestedWordInfo> suggestedWordInfoList) {
-        if (words != null) {
-            mWords = words;
-        } else {
-            mWords = Collections.emptyList();
-        }
+    public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
+            final boolean typedWordValid,
+            final boolean hasAutoCorrectionCandidate,
+            final boolean allowsToBeAutoCorrected,
+            final boolean isPunctuationSuggestions,
+            final boolean isObsoleteSuggestions) {
+        mSuggestedWordInfoList = suggestedWordInfoList;
         mTypedWordValid = typedWordValid;
         mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate;
+        mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
         mIsPunctuationSuggestions = isPunctuationSuggestions;
-        mSuggestedWordInfoList = suggestedWordInfoList;
-        mShouldBlockAutoCorrection = false;
+        mIsObsoleteSuggestions = isObsoleteSuggestions;
     }
 
     public int size() {
-        return mWords.size();
+        return mSuggestedWordInfoList.size();
     }
 
     public CharSequence getWord(int pos) {
-        return mWords.get(pos);
+        return mSuggestedWordInfoList.get(pos).mWord;
+    }
+
+    public SuggestedWordInfo getWordInfo(int pos) {
+        return mSuggestedWordInfoList.get(pos);
     }
 
     public SuggestedWordInfo getInfo(int pos) {
-        return mSuggestedWordInfoList != null ? mSuggestedWordInfoList.get(pos) : null;
+        return mSuggestedWordInfoList.get(pos);
     }
 
     public boolean hasAutoCorrectionWord() {
         return mHasAutoCorrectionCandidate && size() > 1 && !mTypedWordValid;
     }
 
-    public boolean hasWordAboveAutoCorrectionScoreThreshold() {
-        return mHasAutoCorrectionCandidate && ((size() > 1 && !mTypedWordValid) || mTypedWordValid);
+    public boolean willAutoCorrect() {
+        return !mTypedWordValid && mHasAutoCorrectionCandidate;
     }
 
-    public boolean isPunctuationSuggestions() {
-        return mIsPunctuationSuggestions;
+    @Override
+    public String toString() {
+        // Pretty-print method to help debug
+        return "SuggestedWords:"
+                + " mTypedWordValid=" + mTypedWordValid
+                + " mHasAutoCorrectionCandidate=" + mHasAutoCorrectionCandidate
+                + " mAllowsToBeAutoCorrected=" + mAllowsToBeAutoCorrected
+                + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions
+                + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray());
     }
 
-    public void setShouldBlockAutoCorrection() {
-        mShouldBlockAutoCorrection = true;
+    public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
+            final CompletionInfo[] infos) {
+        final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+        for (CompletionInfo info : infos) {
+            if (null != info) result.add(new SuggestedWordInfo(info.getText(),
+                    SuggestedWordInfo.MAX_SCORE));
+        }
+        return result;
     }
 
-    public boolean shouldBlockAutoCorrection() {
-        return mShouldBlockAutoCorrection;
+    // Should get rid of the first one (what the user typed previously) from suggestions
+    // and replace it with what the user currently typed.
+    public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
+            final CharSequence typedWord, final SuggestedWords previousSuggestions) {
+        final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
+        final HashSet<String> alreadySeen = new HashSet<String>();
+        suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE));
+        alreadySeen.add(typedWord.toString());
+        final int previousSize = previousSuggestions.size();
+        for (int pos = 1; pos < previousSize; pos++) {
+            final SuggestedWordInfo prevWordInfo = previousSuggestions.getWordInfo(pos);
+            final String prevWord = prevWordInfo.mWord.toString();
+            // Filter out duplicate suggestion.
+            if (!alreadySeen.contains(prevWord)) {
+                suggestionsList.add(prevWordInfo);
+                alreadySeen.add(prevWord);
+            }
+        }
+        return suggestionsList;
     }
 
-    public static class Builder {
-        private List<CharSequence> mWords = new ArrayList<CharSequence>();
-        private boolean mTypedWordValid;
-        private boolean mHasMinimalSuggestion;
-        private boolean mIsPunctuationSuggestions;
-        private List<SuggestedWordInfo> mSuggestedWordInfoList =
-                new ArrayList<SuggestedWordInfo>();
+    public static class SuggestedWordInfo {
+        public static final int MAX_SCORE = Integer.MAX_VALUE;
+        private final String mWordStr;
+        public final CharSequence mWord;
+        public final int mScore;
+        public final int mCodePointCount;
+        private String mDebugString = "";
 
-        public Builder() {
-            // Nothing to do here.
+        public SuggestedWordInfo(final CharSequence word, final int score) {
+            mWordStr = word.toString();
+            mWord = word;
+            mScore = score;
+            mCodePointCount = mWordStr.codePointCount(0, mWordStr.length());
         }
 
-        public Builder addWords(List<CharSequence> words,
-                List<SuggestedWordInfo> suggestedWordInfoList) {
-            final int N = words.size();
-            for (int i = 0; i < N; ++i) {
-                SuggestedWordInfo suggestedWordInfo = null;
-                if (suggestedWordInfoList != null) {
-                    suggestedWordInfo = suggestedWordInfoList.get(i);
-                }
-                if (suggestedWordInfo == null) {
-                    suggestedWordInfo = new SuggestedWordInfo();
-                }
-                addWord(words.get(i), suggestedWordInfo);
-            }
-            return this;
+
+        public void setDebugString(String str) {
+            if (null == str) throw new NullPointerException("Debug info is null");
+            mDebugString = str;
         }
 
-        public Builder addWord(CharSequence word) {
-            return addWord(word, null, false);
+        public String getDebugString() {
+            return mDebugString;
         }
 
-        public Builder addWord(CharSequence word, CharSequence debugString,
-                boolean isPreviousSuggestedWord) {
-            SuggestedWordInfo info = new SuggestedWordInfo(debugString, isPreviousSuggestedWord);
-            return addWord(word, info);
+        public int codePointCount() {
+            return mCodePointCount;
         }
 
-        private Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) {
-            if (!TextUtils.isEmpty(word)) {
-                mWords.add(word);
-                // It's okay if suggestedWordInfo is null since it's checked where it's used.
-                mSuggestedWordInfoList.add(suggestedWordInfo);
-            }
-            return this;
-        }
-
-        public Builder setApplicationSpecifiedCompletions(CompletionInfo[] infos) {
-            for (CompletionInfo info : infos) {
-                if (null != info) addWord(info.getText());
-            }
-            return this;
-        }
-
-        public Builder setTypedWordValid(boolean typedWordValid) {
-            mTypedWordValid = typedWordValid;
-            return this;
-        }
-
-        public Builder setHasMinimalSuggestion(boolean hasMinimalSuggestion) {
-            mHasMinimalSuggestion = hasMinimalSuggestion;
-            return this;
-        }
-
-        public Builder setIsPunctuationSuggestions() {
-            mIsPunctuationSuggestions = true;
-            return this;
-        }
-
-        // Should get rid of the first one (what the user typed previously) from suggestions
-        // and replace it with what the user currently typed.
-        public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord,
-                SuggestedWords previousSuggestions) {
-            mWords.clear();
-            mSuggestedWordInfoList.clear();
-            final HashSet<String> alreadySeen = new HashSet<String>();
-            addWord(typedWord, null, false);
-            alreadySeen.add(typedWord.toString());
-            final int previousSize = previousSuggestions.size();
-            for (int pos = 1; pos < previousSize; pos++) {
-                final String prevWord = previousSuggestions.getWord(pos).toString();
-                // Filter out duplicate suggestion.
-                if (!alreadySeen.contains(prevWord)) {
-                    addWord(prevWord, null, true);
-                    alreadySeen.add(prevWord);
-                }
-            }
-            mTypedWordValid = false;
-            mHasMinimalSuggestion = false;
-            return this;
-        }
-
-        public SuggestedWords build() {
-            return new SuggestedWords(mWords, mTypedWordValid, mHasMinimalSuggestion,
-                    mIsPunctuationSuggestions, mSuggestedWordInfoList);
-        }
-
-        public int size() {
-            return mWords.size();
-        }
-
-        public CharSequence getWord(int pos) {
-            return mWords.get(pos);
+        public int codePointAt(int i) {
+            return mWordStr.codePointAt(i);
         }
 
         @Override
         public String toString() {
-            // Pretty-print method to help debug
-            final StringBuilder sb = new StringBuilder("StringBuilder: mTypedWordValid = "
-                    + mTypedWordValid + " ; mHasMinimalSuggestion = " + mHasMinimalSuggestion
-                    + " ; mIsPunctuationSuggestions = " + mIsPunctuationSuggestions
-                    + " --- ");
-            for (CharSequence s : mWords) {
-                sb.append(s);
-                sb.append(" ; ");
-            }
-            return sb.toString();
-        }
-    }
-
-    public static class SuggestedWordInfo {
-        private final CharSequence mDebugString;
-        private final boolean mPreviousSuggestedWord;
-
-        public SuggestedWordInfo() {
-            mDebugString = "";
-            mPreviousSuggestedWord = false;
-        }
-
-        public SuggestedWordInfo(CharSequence debugString, boolean previousSuggestedWord) {
-            mDebugString = debugString;
-            mPreviousSuggestedWord = previousSuggestedWord;
-        }
-
-        public String getDebugString() {
-            if (mDebugString == null) {
-                return "";
+            if (TextUtils.isEmpty(mDebugString)) {
+                return mWordStr;
             } else {
-                return mDebugString.toString();
+                return mWordStr + " (" + mDebugString.toString() + ")";
             }
         }
 
-        public boolean isObsoleteSuggestedWord () {
-            return mPreviousSuggestedWord;
+        // TODO: Consolidate this method and StringUtils.removeDupes() in the future.
+        public static void removeDups(ArrayList<SuggestedWordInfo> candidates) {
+            if (candidates.size() <= 1) {
+                return;
+            }
+            int i = 1;
+            while(i < candidates.size()) {
+                final SuggestedWordInfo cur = candidates.get(i);
+                for (int j = 0; j < i; ++j) {
+                    final SuggestedWordInfo previous = candidates.get(j);
+                    if (TextUtils.equals(cur.mWord, previous.mWord)) {
+                        candidates.remove(cur.mScore < previous.mScore ? i : j);
+                        --i;
+                        break;
+                    }
+                }
+                ++i;
+            }
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
deleted file mode 100644
index 79b3bde..0000000
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.Utils.RingCharBuffer;
-
-import android.util.Log;
-
-public class TextEntryState {
-    private static final String TAG = TextEntryState.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private static final int UNKNOWN = 0;
-    private static final int START = 1;
-    private static final int IN_WORD = 2;
-    private static final int ACCEPTED_DEFAULT = 3;
-    private static final int PICKED_SUGGESTION = 4;
-    private static final int PUNCTUATION_AFTER_WORD = 5;
-    private static final int PUNCTUATION_AFTER_ACCEPTED = 6;
-    private static final int SPACE_AFTER_ACCEPTED = 7;
-    private static final int SPACE_AFTER_PICKED = 8;
-    private static final int UNDO_COMMIT = 9;
-    private static final int RECORRECTING = 10;
-    private static final int PICKED_RECORRECTION = 11;
-
-    private static int sState = UNKNOWN;
-    private static int sPreviousState = UNKNOWN;
-
-    private static void setState(final int newState) {
-        sPreviousState = sState;
-        sState = newState;
-    }
-
-    public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord,
-            int separatorCode) {
-        if (typedWord == null) return;
-        setState(ACCEPTED_DEFAULT);
-        LatinImeLogger.logOnAutoCorrection(
-                typedWord.toString(), actualWord.toString(), separatorCode);
-        if (DEBUG)
-            displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord);
-    }
-
-    // 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:
-            setState(ACCEPTED_DEFAULT);
-            break;
-        default:
-            break;
-        }
-        if (DEBUG) displayState("backToAcceptedDefault", "typedWord", typedWord);
-    }
-
-    public static void acceptedTyped(CharSequence typedWord) {
-        setState(PICKED_SUGGESTION);
-        if (DEBUG) displayState("acceptedTyped", "typedWord", typedWord);
-    }
-
-    public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
-        if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
-            setState(PICKED_RECORRECTION);
-        } else {
-            setState(PICKED_SUGGESTION);
-        }
-        if (DEBUG)
-            displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord);
-    }
-
-    public static void selectedForRecorrection() {
-        setState(RECORRECTING);
-        if (DEBUG) displayState("selectedForRecorrection");
-    }
-
-    public static void onAbortRecorrection() {
-        if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
-            setState(START);
-        }
-        if (DEBUG) displayState("onAbortRecorrection");
-    }
-
-    public static void typedCharacter(char c, boolean isSeparator, int x, int y) {
-        final boolean isSpace = (c == Keyboard.CODE_SPACE);
-        switch (sState) {
-        case IN_WORD:
-            if (isSpace || isSeparator) {
-                setState(START);
-            } else {
-                // State hasn't changed.
-            }
-            break;
-        case ACCEPTED_DEFAULT:
-        case SPACE_AFTER_PICKED:
-        case PUNCTUATION_AFTER_ACCEPTED:
-            if (isSpace) {
-                setState(SPACE_AFTER_ACCEPTED);
-            } else if (isSeparator) {
-                // Swap
-                setState(PUNCTUATION_AFTER_ACCEPTED);
-            } else {
-                setState(IN_WORD);
-            }
-            break;
-        case PICKED_SUGGESTION:
-        case PICKED_RECORRECTION:
-            if (isSpace) {
-                setState(SPACE_AFTER_PICKED);
-            } else if (isSeparator) {
-                // Swap
-                setState(PUNCTUATION_AFTER_ACCEPTED);
-            } else {
-                setState(IN_WORD);
-            }
-            break;
-        case START:
-        case UNKNOWN:
-        case SPACE_AFTER_ACCEPTED:
-        case PUNCTUATION_AFTER_WORD:
-            if (!isSpace && !isSeparator) {
-                setState(IN_WORD);
-            } else {
-                setState(START);
-            }
-            break;
-        case UNDO_COMMIT:
-            if (isSpace || isSeparator) {
-                setState(START);
-            } else {
-                setState(IN_WORD);
-            }
-            break;
-        case RECORRECTING:
-            setState(START);
-            break;
-        }
-        RingCharBuffer.getInstance().push(c, x, y);
-        if (isSeparator) {
-            LatinImeLogger.logOnInputSeparator();
-        } else {
-            LatinImeLogger.logOnInputChar();
-        }
-        if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator);
-    }
-    
-    public static void backspace() {
-        if (sState == ACCEPTED_DEFAULT) {
-            setState(UNDO_COMMIT);
-            LatinImeLogger.logOnAutoCorrectionCancelled();
-        } else if (sState == UNDO_COMMIT) {
-            setState(IN_WORD);
-        }
-        if (DEBUG) displayState("backspace");
-    }
-
-    public static void reset() {
-        setState(START);
-        if (DEBUG) displayState("reset");
-    }
-
-    public static boolean isAcceptedDefault() {
-        return sState == ACCEPTED_DEFAULT;
-    }
-
-    public static boolean isSpaceAfterPicked() {
-        return sState == SPACE_AFTER_PICKED;
-    }
-
-    public static boolean isUndoCommit() {
-        return sState == UNDO_COMMIT;
-    }
-
-    public static boolean isPunctuationAfterAccepted() {
-        return sState == PUNCTUATION_AFTER_ACCEPTED;
-    }
-
-    public static boolean isRecorrecting() {
-        return sState == RECORRECTING || sState == PICKED_RECORRECTION;
-    }
-
-    public static String getState() {
-        return stateName(sState);
-    }
-
-    private static String stateName(int state) {
-        switch (state) {
-        case START: return "START";
-        case IN_WORD: return "IN_WORD";
-        case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT";
-        case PICKED_SUGGESTION: return "PICKED_SUGGESTION";
-        case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD";
-        case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED";
-        case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED";
-        case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED";
-        case UNDO_COMMIT: return "UNDO_COMMIT";
-        case RECORRECTING: return "RECORRECTING";
-        case PICKED_RECORRECTION: return "PICKED_RECORRECTION";
-        default: return "UNKNOWN";
-        }
-    }
-
-    private static void displayState(String title, Object ... args) {
-        final StringBuilder sb = new StringBuilder(title);
-        sb.append(':');
-        for (int i = 0; i < args.length; i += 2) {
-            sb.append(' ');
-            sb.append(args[i]);
-            sb.append('=');
-            sb.append(args[i+1].toString());
-        }
-        sb.append(" state=");
-        sb.append(stateName(sState));
-        sb.append(" previous=");
-        sb.append(stateName(sPreviousState));
-        Log.d(TAG, sb.toString());
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 0bbbf39..51b9933 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -18,12 +18,10 @@
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.database.ContentObserver;
 import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
 import android.provider.UserDictionary.Words;
 import android.text.TextUtils;
 
@@ -38,11 +36,9 @@
         Words.FREQUENCY,
     };
 
-    private static final String[] PROJECTION_ADD = {
-        Words._ID,
-        Words.FREQUENCY,
-        Words.LOCALE,
-    };
+    // This is not exported by the framework so we pretty much have to write it here verbatim
+    private static final String ACTION_USER_DICTIONARY_INSERT =
+            "com.android.settings.USER_DICTIONARY_INSERT";
 
     private ContentObserver mObserver;
     final private String mLocale;
@@ -134,7 +130,11 @@
         final Cursor cursor = getContext().getContentResolver()
                 .query(Words.CONTENT_URI, PROJECTION_QUERY, request.toString(),
                         requestArguments, null);
-        addWords(cursor);
+        try {
+            addWords(cursor);
+        } finally {
+            if (null != cursor) cursor.close();
+        }
     }
 
     public boolean isEnabled() {
@@ -160,57 +160,17 @@
     public synchronized void addWord(final String word, final int frequency) {
         // Force load the dictionary here synchronously
         if (getRequiresReload()) loadDictionaryAsync();
+        // TODO: do something for the UI. With the following, any sufficiently long word will
+        // look like it will go to the user dictionary but it won't.
         // Safeguard against adding long words. Can cause stack overflow.
         if (word.length() >= getMaxWordLength()) return;
 
-        super.addWord(word, frequency);
-
-        // Update the user dictionary provider
-        final ContentValues values = new ContentValues(5);
-        values.put(Words.WORD, word);
-        values.put(Words.FREQUENCY, frequency);
-        values.put(Words.LOCALE, mLocale);
-        values.put(Words.APP_ID, 0);
-
-        final ContentResolver contentResolver = getContext().getContentResolver();
-        final ContentProviderClient client =
-                contentResolver.acquireContentProviderClient(Words.CONTENT_URI);
-        if (null == client) return;
-        new Thread("addWord") {
-            @Override
-            public void run() {
-                Cursor cursor = null;
-                try {
-                    cursor = client.query(Words.CONTENT_URI, PROJECTION_ADD,
-                            "word=? and ((locale IS NULL) or (locale=?))",
-                                    new String[] { word, mLocale }, null);
-                    if (cursor != null && cursor.moveToFirst()) {
-                        final String locale = cursor.getString(cursor.getColumnIndex(Words.LOCALE));
-                        // If locale is null, we will not override the entry.
-                        if (locale != null && locale.equals(mLocale.toString())) {
-                            final long id = cursor.getLong(cursor.getColumnIndex(Words._ID));
-                            final Uri uri =
-                                    Uri.withAppendedPath(Words.CONTENT_URI, Long.toString(id));
-                            // Update the entry with new frequency value.
-                            client.update(uri, values, null, null);
-                        }
-                    } else {
-                        // Insert new entry.
-                        client.insert(Words.CONTENT_URI, values);
-                    }
-                } catch (RemoteException e) {
-                    // If we come here, the activity is already about to be killed, and we
-                    // have no means of contacting the content provider any more.
-                    // See ContentResolver#insert, inside the catch(){}
-                } finally {
-                    if (null != cursor) cursor.close();
-                    client.release();
-                }
-            }
-        }.start();
-
-        // In case the above does a synchronous callback of the change observer
-        setRequiresReload(false);
+        // TODO: Add an argument to the intent to specify the frequency.
+        Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
+        intent.putExtra(Words.WORD, word);
+        intent.putExtra(Words.LOCALE, mLocale);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
     }
 
     @Override
@@ -242,6 +202,5 @@
                 cursor.moveToNext();
             }
         }
-        cursor.close();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
similarity index 71%
rename from java/src/com/android/inputmethod/latin/UserBigramDictionary.java
rename to java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 9e65667..62525c2 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -31,12 +31,11 @@
 import java.util.Iterator;
 
 /**
- * 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.
+ * Locally gathers stats about the words user types and various other signals like auto-correction
+ * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
  */
-public class UserBigramDictionary extends ExpandableDictionary {
-    private static final String TAG = "UserBigramDictionary";
+public class UserHistoryDictionary extends ExpandableDictionary {
+    private static final String TAG = "UserHistoryDictionary";
 
     /** Any pair being typed or picked */
     private static final int FREQUENCY_FOR_TYPED = 2;
@@ -45,14 +44,14 @@
     private static final int FREQUENCY_MAX = 127;
 
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    private static int sMaxUserBigrams = 10000;
+    private static int sMaxHistoryBigrams = 10000;
 
     /**
      * When it hits maximum bigram pair, it will delete until you are left with
-     * only (sMaxUserBigrams - sDeleteUserBigrams) pairs.
+     * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
      * Do not keep this number small to avoid deleting too often.
      */
-    private static int sDeleteUserBigrams = 1000;
+    private static int sDeleteHistoryBigrams = 1000;
 
     /**
      * Database version should increase if the database structure changes
@@ -64,7 +63,7 @@
     /** 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)
+    // 2-5 character string.
     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";
@@ -76,8 +75,6 @@
     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;
 
@@ -114,8 +111,16 @@
 
         @Override
         public boolean equals(Object bigram) {
-            Bigram bigram2 = (Bigram) bigram;
-            return (mWord1.equals(bigram2.mWord1) && mWord2.equals(bigram2.mWord2));
+            if (!(bigram instanceof Bigram)) {
+                return false;
+            }
+            final Bigram bigram2 = (Bigram) bigram;
+            final boolean eq1 =
+                    mWord1 == null ? bigram2.mWord1 == null : mWord1.equals(bigram2.mWord1);
+            if (!eq1) {
+                return false;
+            }
+            return mWord2 == null ? bigram2.mWord2 == null : mWord2.equals(bigram2.mWord2);
         }
 
         @Override
@@ -124,17 +129,16 @@
         }
     }
 
-    public void setDatabaseMax(int maxUserBigram) {
-        sMaxUserBigrams = maxUserBigram;
+    public void setDatabaseMax(int maxHistoryBigram) {
+        sMaxHistoryBigrams = maxHistoryBigram;
     }
 
-    public void setDatabaseDelete(int deleteUserBigram) {
-        sDeleteUserBigrams = deleteUserBigram;
+    public void setDatabaseDelete(int deleteHistoryBigram) {
+        sDeleteHistoryBigrams = deleteHistoryBigram;
     }
 
-    public UserBigramDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
+    public UserHistoryDictionary(final Context context, final String locale, final int dicTypeId) {
         super(context, dicTypeId);
-        mIme = ime;
         mLocale = locale;
         if (sOpenHelper == null) {
             sOpenHelper = new DatabaseHelper(getContext());
@@ -155,19 +159,35 @@
     }
 
     /**
-     * Pair will be added to the userbigram database.
+     * Return whether the passed charsequence is in the dictionary.
      */
-    public int addBigrams(String word1, String word2) {
-        // remove caps if second word is autocapitalized
-        if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
-            word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
-        }
+    @Override
+    public boolean isValidWord(final CharSequence word) {
+        // TODO: figure out what is the correct thing to do here.
+        return false;
+    }
+
+    /**
+     * Pair will be added to the user history dictionary.
+     *
+     * The first word may be null. That means we don't know the context, in other words,
+     * it's only a unigram. The first word may also be an empty string : this means start
+     * context, as in beginning of a sentence for example.
+     * The second word may not be null (a NullPointerException would be thrown).
+     */
+    public int addToUserHistory(final String word1, String word2) {
+        super.addWord(word2, FREQUENCY_FOR_TYPED);
         // Do not insert a word as a bigram of itself
-        if (word1.equals(word2)) {
+        if (word2.equals(word1)) {
             return 0;
         }
 
-        int freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
+        int freq;
+        if (null == word1) {
+            freq = FREQUENCY_FOR_TYPED;
+        } else {
+            freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
+        }
         if (freq > FREQUENCY_MAX) freq = FREQUENCY_MAX;
         synchronized (mPendingWritesLock) {
             if (freq == FREQUENCY_FOR_TYPED || mPendingWrites.isEmpty()) {
@@ -212,7 +232,8 @@
     @Override
     public void loadDictionaryAsync() {
         // Load the words that correspond to the current input locale
-        Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
+        final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
+        if (null == cursor) return;
         try {
             if (cursor.moveToFirst()) {
                 int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
@@ -224,7 +245,10 @@
                     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) {
+                    if (null == word1) {
+                        super.addWord(word2, frequency);
+                    } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
+                            && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
                         super.setBigram(word1, word2, frequency);
                     }
                     cursor.moveToNext();
@@ -238,7 +262,7 @@
     /**
      * Query the database
      */
-    private Cursor query(String selection, String[] selectionArgs) {
+    private static Cursor query(String selection, String[] selectionArgs) {
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 
         // main INNER JOIN frequency ON (main._id=freq.pair_id)
@@ -249,11 +273,17 @@
         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;
+        try {
+            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;
+        } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+            // Can't open the database : presumably we can't access storage. That may happen
+            // when the device is wedged; do a best effort to still start the keyboard.
+            return null;
+        }
     }
 
     /**
@@ -310,15 +340,15 @@
         }
 
         /** Prune any old data if the database is getting too big. */
-        private void checkPruneData(SQLiteDatabase db) {
+        private static 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;
+                if (totalRowCount > sMaxHistoryBigrams) {
+                    int numDeleteRows = (totalRowCount - sMaxHistoryBigrams) + sDeleteHistoryBigrams;
                     int pairIdColumnId = c.getColumnIndex(FREQ_COLUMN_PAIR_ID);
                     c.moveToFirst();
                     int count = 0;
@@ -344,18 +374,39 @@
 
         @Override
         protected Void doInBackground(Void... v) {
-            SQLiteDatabase db = mDbHelper.getWritableDatabase();
+            SQLiteDatabase db = null;
+            try {
+                db = mDbHelper.getWritableDatabase();
+            } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+                // If we can't open the db, don't do anything. Exit through the next test
+                // for non-nullity of the db variable.
+            }
+            if (null == db) {
+                // Not much we can do. Just exit.
+                sUpdatingDB = false;
+                return null;
+            }
             db.execSQL("PRAGMA foreign_keys = ON;");
             // Write all the entries to the db
             Iterator<Bigram> iterator = mMap.iterator();
             while (iterator.hasNext()) {
+                // TODO: this process of making a text search for each pair each time
+                // is terribly inefficient. Optimize this.
                 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.mWord1, bi.mWord2, mLocale }, null, null, null);
+                final Cursor c;
+                if (null != bi.mWord1) {
+                    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.mWord1, bi.mWord2, mLocale }, null, null, null);
+                } else {
+                    c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
+                            MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2 + "=? AND "
+                            + MAIN_COLUMN_LOCALE + "=?",
+                            new String[] { bi.mWord2, mLocale }, null, null, null);
+                }
 
                 int pairId;
                 if (c.moveToFirst()) {
@@ -380,7 +431,7 @@
             return null;
         }
 
-        private ContentValues getContentValues(String word1, String word2, String locale) {
+        private static 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);
@@ -388,7 +439,7 @@
             return values;
         }
 
-        private ContentValues getFrequencyContentValues(int pairId, int frequency) {
+        private static ContentValues getFrequencyContentValues(int pairId, int frequency) {
            ContentValues values = new ContentValues(2);
            values.put(FREQ_COLUMN_PAIR_ID, pairId);
            values.put(FREQ_COLUMN_FREQUENCY, frequency);
diff --git a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
deleted file mode 100644
index e41230b..0000000
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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.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;
-
-import java.util.HashMap;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * This class (inherited from the old AutoDictionary) is used for user history
- * based dictionary. It stores words that the user typed to supply a provision
- * for suggesting and re-ordering of candidates.
- */
-public class UserUnigramDictionary extends ExpandableDictionary {
-    static final boolean ENABLE_USER_UNIGRAM_DICTIONARY = false;
-
-    // Weight added to a user picking a new word from the suggestion strip
-    static final int FREQUENCY_FOR_PICKED = 3;
-    // Weight added to a user typing a new word that doesn't get corrected (or is reverted)
-    static final int FREQUENCY_FOR_TYPED = 1;
-    // If the user touches a typed word 2 times or more, it will become valid.
-    private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED;
-
-    private LatinIME mIme;
-    // Locale for which this user unigram dictionary is storing words
-    private String mLocale;
-
-    private HashMap<String,Integer> mPendingWrites = new HashMap<String,Integer>();
-    private final Object mPendingWritesLock = new Object();
-
-    // TODO: we should probably change the database name
-    private static final String DATABASE_NAME = "auto_dict.db";
-    private static final int DATABASE_VERSION = 1;
-
-    // These are the columns in the dictionary
-    // TODO: Consume less space by using a unique id for locale instead of the whole
-    // 2-5 character string.
-    private static final String COLUMN_ID = BaseColumns._ID;
-    private static final String COLUMN_WORD = "word";
-    private static final String COLUMN_FREQUENCY = "freq";
-    private static final String COLUMN_LOCALE = "locale";
-
-    /** Sort by descending order of frequency. */
-    public static final String DEFAULT_SORT_ORDER = COLUMN_FREQUENCY + " DESC";
-
-    /** Name of the words table in the database */
-    private static final String USER_UNIGRAM_DICT_TABLE_NAME = "words";
-
-    private static HashMap<String, String> sDictProjectionMap;
-
-    static {
-        if (ENABLE_USER_UNIGRAM_DICTIONARY) {
-            sDictProjectionMap = new HashMap<String, String>();
-            sDictProjectionMap.put(COLUMN_ID, COLUMN_ID);
-            sDictProjectionMap.put(COLUMN_WORD, COLUMN_WORD);
-            sDictProjectionMap.put(COLUMN_FREQUENCY, COLUMN_FREQUENCY);
-            sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE);
-        }
-    }
-
-    private static DatabaseHelper sOpenHelper = null;
-
-    public UserUnigramDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
-        super(context, dicTypeId);
-        // Super must be first statement of the constructor... I'd like not to do it if the
-        // user unigram dictionary is not enabled, but Java won't let me.
-        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
-        mIme = ime;
-        mLocale = locale;
-        if (sOpenHelper == null) {
-            sOpenHelper = new DatabaseHelper(getContext());
-        }
-        if (mLocale != null && mLocale.length() > 1) {
-            loadDictionary();
-        }
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return false;
-        final int frequency = getWordFrequency(word);
-        return frequency >= VALIDITY_THRESHOLD;
-    }
-
-    @Override
-    public void close() {
-        super.close();
-        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
-        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();
-    }
-
-    @Override
-    public void loadDictionaryAsync() {
-        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
-        // Load the words that correspond to the current input locale
-        Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
-        try {
-            if (cursor.moveToFirst()) {
-                int wordIndex = cursor.getColumnIndex(COLUMN_WORD);
-                int frequencyIndex = cursor.getColumnIndex(COLUMN_FREQUENCY);
-                while (!cursor.isAfterLast()) {
-                    String word = cursor.getString(wordIndex);
-                    int frequency = cursor.getInt(frequencyIndex);
-                    // Safeguard against adding really long words. Stack may overflow due
-                    // to recursive lookup
-                    if (word.length() < getMaxWordLength()) {
-                        super.addWord(word, frequency);
-                    }
-                    cursor.moveToNext();
-                }
-            }
-        } finally {
-            cursor.close();
-        }
-    }
-
-    @Override
-    public void addWord(String newWord, int addFrequency) {
-        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
-        String word = newWord;
-        final int length = word.length();
-        // Don't add very short or very long words.
-        if (length < 2 || length > getMaxWordLength()) return;
-        if (mIme.getCurrentWord().isAutoCapitalized()) {
-            // Remove caps before adding
-            word = Character.toLowerCase(word.charAt(0)) + word.substring(1);
-        }
-        int freq = getWordFrequency(word);
-        freq = freq < 0 ? addFrequency : freq + addFrequency;
-        super.addWord(word, freq);
-
-        synchronized (mPendingWritesLock) {
-            // Write a null frequency if it is to be deleted from the db
-            mPendingWrites.put(word, freq == 0 ? null : new Integer(freq));
-        }
-    }
-
-    /**
-     * Schedules a background thread to write any pending words to the database.
-     */
-    public void flushPendingWrites() {
-        if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
-        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 HashMap<String, Integer>();
-        }
-    }
-
-    /**
-     * 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("CREATE TABLE " + USER_UNIGRAM_DICT_TABLE_NAME + " ("
-                    + COLUMN_ID + " INTEGER PRIMARY KEY,"
-                    + COLUMN_WORD + " TEXT,"
-                    + COLUMN_FREQUENCY + " INTEGER,"
-                    + COLUMN_LOCALE + " TEXT"
-                    + ");");
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            Log.w("UserUnigramDictionary", "Upgrading database from version " + oldVersion + " to "
-                    + newVersion + ", which will destroy all old data");
-            db.execSQL("DROP TABLE IF EXISTS " + USER_UNIGRAM_DICT_TABLE_NAME);
-            onCreate(db);
-        }
-    }
-
-    private Cursor query(String selection, String[] selectionArgs) {
-        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables(USER_UNIGRAM_DICT_TABLE_NAME);
-        qb.setProjectionMap(sDictProjectionMap);
-
-        // Get the database and run the query
-        SQLiteDatabase db = sOpenHelper.getReadableDatabase();
-        Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
-                DEFAULT_SORT_ORDER);
-        return c;
-    }
-
-    /**
-     * 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 HashMap<String, Integer> mMap;
-        private final DatabaseHelper mDbHelper;
-        private final String mLocale;
-
-        public UpdateDbTask(@SuppressWarnings("unused") Context context, DatabaseHelper openHelper,
-                HashMap<String, Integer> pendingWrites, String locale) {
-            mMap = pendingWrites;
-            mLocale = locale;
-            mDbHelper = openHelper;
-        }
-
-        @Override
-        protected Void doInBackground(Void... v) {
-            SQLiteDatabase db = mDbHelper.getWritableDatabase();
-            // Write all the entries to the db
-            Set<Entry<String,Integer>> mEntries = mMap.entrySet();
-            for (Entry<String,Integer> entry : mEntries) {
-                Integer freq = entry.getValue();
-                db.delete(USER_UNIGRAM_DICT_TABLE_NAME, COLUMN_WORD + "=? AND " + COLUMN_LOCALE
-                        + "=?", new String[] { entry.getKey(), mLocale });
-                if (freq != null) {
-                    db.insert(USER_UNIGRAM_DICT_TABLE_NAME, null,
-                            getContentValues(entry.getKey(), freq, mLocale));
-                }
-            }
-            return null;
-        }
-
-        private ContentValues getContentValues(String word, int frequency, String locale) {
-            ContentValues values = new ContentValues(4);
-            values.put(COLUMN_WORD, word);
-            values.put(COLUMN_FREQUENCY, frequency);
-            values.put(COLUMN_LOCALE, locale);
-            return values;
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index b29ff19..0485c88 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -17,48 +17,39 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
-import android.content.SharedPreferences;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
-import android.text.InputType;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
-import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
 import java.util.Date;
-import java.util.List;
-import java.util.Locale;
+import java.util.HashMap;
 
 public class Utils {
-    private static final String TAG = Utils.class.getSimpleName();
-    private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
-    private static boolean DBG = LatinImeLogger.sDBG;
-    private static boolean DBG_EDIT_DISTANCE = false;
-
     private Utils() {
-        // Intentional empty constructor for utility class.
+        // This utility class is not publicly instantiable.
     }
 
     /**
@@ -112,108 +103,6 @@
         }
     }
 
-    public static boolean hasMultipleEnabledIMEsOrSubtypes(
-            final InputMethodManagerCompatWrapper imm,
-            final boolean shouldIncludeAuxiliarySubtypes) {
-        final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
-
-        // Number of the filtered IMEs
-        int filteredImisCount = 0;
-
-        for (InputMethodInfoCompatWrapper imi : enabledImis) {
-            // We can return true immediately after we find two or more filtered IMEs.
-            if (filteredImisCount > 1) return true;
-            final List<InputMethodSubtypeCompatWrapper> subtypes =
-                    imm.getEnabledInputMethodSubtypeList(imi, true);
-            // IMEs that have no subtypes should be counted.
-            if (subtypes.isEmpty()) {
-                ++filteredImisCount;
-                continue;
-            }
-
-            int auxCount = 0;
-            for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
-                if (subtype.isAuxiliary()) {
-                    ++auxCount;
-                }
-            }
-            final int nonAuxCount = subtypes.size() - auxCount;
-
-            // IMEs that have one or more non-auxiliary subtypes should be counted.
-            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
-            // subtypes should be counted as well.
-            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
-                ++filteredImisCount;
-                continue;
-            }
-        }
-
-        return filteredImisCount > 1
-        // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
-        // input method subtype (The current IME should be LatinIME.)
-                || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
-    }
-
-    public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
-        return getInputMethodInfo(imm, packageName).getId();
-    }
-
-    public static InputMethodInfoCompatWrapper getInputMethodInfo(
-            InputMethodManagerCompatWrapper imm, String packageName) {
-        for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
-            if (imi.getPackageName().equals(packageName))
-                return imi;
-        }
-        throw new RuntimeException("Can not find input method id for " + packageName);
-    }
-
-    // TODO: Resolve the inconsistencies between the native auto correction algorithms and
-    // this safety net
-    public static boolean shouldBlockAutoCorrectionBySafetyNet(SuggestedWords suggestions,
-            Suggest suggest) {
-        // Safety net for auto correction.
-        // Actually if we hit this safety net, it's actually a bug.
-        if (suggestions.size() <= 1 || suggestions.mTypedWordValid) return false;
-        // If user selected aggressive auto correction mode, there is no need to use the safety
-        // net.
-        if (suggest.isAggressiveAutoCorrectionMode()) return false;
-        final CharSequence typedWord = suggestions.getWord(0);
-        // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
-        // we should not use net because relatively edit distance can be big.
-        if (typedWord.length() < MINIMUM_SAFETY_NET_CHAR_LENGTH) return false;
-        final CharSequence suggestionWord = suggestions.getWord(1);
-        final int typedWordLength = typedWord.length();
-        final int maxEditDistanceOfNativeDictionary =
-                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
-        final int distance = Utils.editDistance(typedWord, suggestionWord);
-        if (DBG) {
-            Log.d(TAG, "Autocorrected edit distance = " + distance
-                    + ", " + maxEditDistanceOfNativeDictionary);
-        }
-        if (distance > maxEditDistanceOfNativeDictionary) {
-            if (DBG) {
-                Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestionWord);
-                Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
-                        + "Turning off auto-correction.");
-            }
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public static boolean canBeFollowedByPeriod(final int codePoint) {
-        // TODO: Check again whether there really ain't a better way to check this.
-        // TODO: This should probably be language-dependant...
-        return Character.isLetterOrDigit(codePoint)
-                || codePoint == Keyboard.CODE_SINGLE_QUOTE
-                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
-                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
-                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
-                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
-                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
-    }
-
     /* package */ static class RingCharBuffer {
         private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
         private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -221,7 +110,6 @@
         /* package */ static final int BUFSIZE = 20;
         private InputMethodService mContext;
         private boolean mEnabled = false;
-        private boolean mUsabilityStudy = false;
         private int mEnd = 0;
         /* package */ int mLength = 0;
         private char[] mCharBuf = new char[BUFSIZE];
@@ -238,19 +126,16 @@
                 boolean usabilityStudy) {
             sRingCharBuffer.mContext = context;
             sRingCharBuffer.mEnabled = enabled || usabilityStudy;
-            sRingCharBuffer.mUsabilityStudy = usabilityStudy;
             UsabilityStudyLogUtils.getInstance().init(context);
             return sRingCharBuffer;
         }
-        private int normalize(int in) {
+        private static int normalize(int in) {
             int ret = in % BUFSIZE;
             return ret < 0 ? ret + BUFSIZE : ret;
         }
+        // TODO: accept code points
         public void push(char c, int x, int y) {
             if (!mEnabled) return;
-            if (mUsabilityStudy) {
-                UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
-            }
             mCharBuf[mEnd] = c;
             mXBuf[mEnd] = x;
             mYBuf[mEnd] = y;
@@ -317,49 +202,6 @@
         }
     }
 
-
-    /* Damerau-Levenshtein distance */
-    public static int editDistance(CharSequence s, CharSequence t) {
-        if (s == null || t == null) {
-            throw new IllegalArgumentException("editDistance: Arguments should not be null.");
-        }
-        final int sl = s.length();
-        final int tl = t.length();
-        int[][] dp = new int [sl + 1][tl + 1];
-        for (int i = 0; i <= sl; i++) {
-            dp[i][0] = i;
-        }
-        for (int j = 0; j <= tl; j++) {
-            dp[0][j] = j;
-        }
-        for (int i = 0; i < sl; ++i) {
-            for (int j = 0; j < tl; ++j) {
-                final char sc = Character.toLowerCase(s.charAt(i));
-                final char tc = Character.toLowerCase(t.charAt(j));
-                final int cost = sc == tc ? 0 : 1;
-                dp[i + 1][j + 1] = Math.min(
-                        dp[i][j + 1] + 1, Math.min(dp[i + 1][j] + 1, dp[i][j] + cost));
-                // Overwrite for transposition cases
-                if (i > 0 && j > 0
-                        && sc == Character.toLowerCase(t.charAt(j - 1))
-                        && tc == Character.toLowerCase(s.charAt(i - 1))) {
-                    dp[i + 1][j + 1] = Math.min(dp[i + 1][j + 1], dp[i - 1][j - 1] + cost);
-                }
-            }
-        }
-        if (DBG_EDIT_DISTANCE) {
-            Log.d(TAG, "editDistance:" + s + "," + t);
-            for (int i = 0; i < dp.length; ++i) {
-                StringBuffer sb = new StringBuffer();
-                for (int j = 0; j < dp[i].length; ++j) {
-                    sb.append(dp[i][j]).append(',');
-                }
-                Log.d(TAG, i + ":" + sb.toString());
-            }
-        }
-        return dp[sl][tl];
-    }
-
     // Get the current stack trace
     public static String getStackTrace() {
         StringBuilder sb = new StringBuilder();
@@ -373,56 +215,8 @@
         return sb.toString();
     }
 
-    // In dictionary.cpp, getSuggestion() method,
-    // suggestion scores are computed using the below formula.
-    // original score
-    //  := pow(mTypedLetterMultiplier (this is defined 2),
-    //         (the number of matched characters between typed word and suggested word))
-    //     * (individual word's score which defined in the unigram dictionary,
-    //         and this score is defined in range [0, 255].)
-    // Then, the following processing is applied.
-    //     - If the dictionary word is matched up to the point of the user entry
-    //       (full match up to min(before.length(), after.length())
-    //       => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
-    //     - If the word is a true full match except for differences in accents or
-    //       capitalization, then treat it as if the score was 255.
-    //     - If before.length() == after.length()
-    //       => multiply by mFullWordMultiplier (this is defined 2))
-    // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
-    // For historical reasons we ignore the 1.2 modifier (because the measure for a good
-    // autocorrection threshold was done at a time when it didn't exist). This doesn't change
-    // the result.
-    // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
-    private static final int MAX_INITIAL_SCORE = 255;
-    private static final int TYPED_LETTER_MULTIPLIER = 2;
-    private static final int FULL_WORD_MULTIPLIER = 2;
-    private static final int S_INT_MAX = 2147483647;
-    public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
-        final int beforeLength = before.length();
-        final int afterLength = after.length();
-        if (beforeLength == 0 || afterLength == 0) return 0;
-        final int distance = editDistance(before, after);
-        // If afterLength < beforeLength, the algorithm is suggesting a word by excessive character
-        // correction.
-        int spaceCount = 0;
-        for (int i = 0; i < afterLength; ++i) {
-            if (after.charAt(i) == Keyboard.CODE_SPACE) {
-                ++spaceCount;
-            }
-        }
-        if (spaceCount == afterLength) return 0;
-        final double maximumScore = score == S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
-                * Math.pow(
-                        TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength - spaceCount))
-                * FULL_WORD_MULTIPLIER;
-        // add a weight based on edit distance.
-        // distance <= max(afterLength, beforeLength) == afterLength,
-        // so, 0 <= distance / afterLength <= 1
-        final double weight = 1.0 - (double) distance / afterLength;
-        return (score / maximumScore) * weight;
-    }
-
     public static class UsabilityStudyLogUtils {
+        // TODO: remove code duplication with ResearchLog class
         private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
         private static final String FILENAME = "log.txt";
         private static final UsabilityStudyLogUtils sInstance =
@@ -437,7 +231,7 @@
 
         private UsabilityStudyLogUtils() {
             mDate = new Date();
-            mDateFormat = new SimpleDateFormat("dd MMM HH:mm:ss.SSS");
+            mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
 
             HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
                     Process.THREAD_PRIORITY_BACKGROUND);
@@ -465,8 +259,8 @@
             }
         }
 
-        public void writeBackSpace() {
-            UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
+        public static void writeBackSpace(int x, int y) {
+            UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
         }
 
         public void writeChar(char c, int x, int y) {
@@ -504,32 +298,89 @@
             });
         }
 
+        private synchronized String getBufferedLogs() {
+            mWriter.flush();
+            StringBuilder sb = new StringBuilder();
+            BufferedReader br = getBufferedReader();
+            String line;
+            try {
+                while ((line = br.readLine()) != null) {
+                    sb.append('\n');
+                    sb.append(line);
+                }
+            } catch (IOException e) {
+                Log.e(USABILITY_TAG, "Can't read log file.");
+            } finally {
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
+                }
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    // ignore.
+                }
+            }
+            return sb.toString();
+        }
+
+        public void emailResearcherLogsAll() {
+            mLoggingHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    final Date date = new Date();
+                    date.setTime(System.currentTimeMillis());
+                    final String currentDateTimeString =
+                            new SimpleDateFormat("yyyyMMdd-HHmmssZ").format(date);
+                    if (mFile == null) {
+                        Log.w(USABILITY_TAG, "No internal log file found.");
+                        return;
+                    }
+                    if (mIms.checkCallingOrSelfPermission(
+                                android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                                        != PackageManager.PERMISSION_GRANTED) {
+                        Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+                        return;
+                    }
+                    mWriter.flush();
+                    final String destPath = Environment.getExternalStorageDirectory()
+                            + "/research-" + currentDateTimeString + ".log";
+                    final File destFile = new File(destPath);
+                    try {
+                        final FileChannel src = (new FileInputStream(mFile)).getChannel();
+                        final FileChannel dest = (new FileOutputStream(destFile)).getChannel();
+                        src.transferTo(0, src.size(), dest);
+                        src.close();
+                        dest.close();
+                    } catch (FileNotFoundException e1) {
+                        Log.w(USABILITY_TAG, e1);
+                        return;
+                    } catch (IOException e2) {
+                        Log.w(USABILITY_TAG, e2);
+                        return;
+                    }
+                    if (destFile == null || !destFile.exists()) {
+                        Log.w(USABILITY_TAG, "Dest file doesn't exist.");
+                        return;
+                    }
+                    final Intent intent = new Intent(Intent.ACTION_SEND);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    if (LatinImeLogger.sDBG) {
+                        Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
+                    }
+                    intent.setType("text/plain");
+                    intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
+                    intent.putExtra(Intent.EXTRA_SUBJECT,
+                            "[Research Logs] " + currentDateTimeString);
+                    mIms.startActivity(intent);
+                }
+            });
+        }
+
         public void printAll() {
             mLoggingHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mWriter.flush();
-                    StringBuilder sb = new StringBuilder();
-                    BufferedReader br = getBufferedReader();
-                    String line;
-                    try {
-                        while ((line = br.readLine()) != null) {
-                            sb.append('\n');
-                            sb.append(line);
-                        }
-                    } catch (IOException e) {
-                        Log.e(USABILITY_TAG, "Can't read log file.");
-                    } finally {
-                        if (LatinImeLogger.sDBG) {
-                            Log.d(USABILITY_TAG, "output all logs\n" + sb.toString());
-                        }
-                        mIms.getCurrentInputConnection().commitText(sb.toString(), 0);
-                        try {
-                            br.close();
-                        } catch (IOException e) {
-                            // ignore.
-                        }
-                    }
+                    mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
                 }
             });
         }
@@ -570,134 +421,6 @@
         }
     }
 
-    public static int getKeyboardMode(EditorInfo editorInfo) {
-        if (editorInfo == null)
-            return KeyboardId.MODE_TEXT;
-
-        final int inputType = editorInfo.inputType;
-        final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-
-        switch (inputType & InputType.TYPE_MASK_CLASS) {
-        case InputType.TYPE_CLASS_NUMBER:
-        case InputType.TYPE_CLASS_DATETIME:
-            return KeyboardId.MODE_NUMBER;
-        case InputType.TYPE_CLASS_PHONE:
-            return KeyboardId.MODE_PHONE;
-        case InputType.TYPE_CLASS_TEXT:
-            if (InputTypeCompatUtils.isEmailVariation(variation)) {
-                return KeyboardId.MODE_EMAIL;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
-                return KeyboardId.MODE_URL;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
-                return KeyboardId.MODE_IM;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
-                return KeyboardId.MODE_TEXT;
-            } else {
-                return KeyboardId.MODE_TEXT;
-            }
-        default:
-            return KeyboardId.MODE_TEXT;
-        }
-    }
-
-    public static boolean containsInCsv(String key, String csv) {
-        if (csv == null)
-            return false;
-        for (String option : csv.split(",")) {
-            if (option.equals(key))
-                return true;
-        }
-        return false;
-    }
-
-    public static boolean inPrivateImeOptions(String packageName, String key,
-            EditorInfo editorInfo) {
-        if (editorInfo == null)
-            return false;
-        return containsInCsv(packageName != null ? packageName + "." + key : key,
-                editorInfo.privateImeOptions);
-    }
-
-    /**
-     * Returns a main dictionary resource id
-     * @return main dictionary resource id
-     */
-    public static int getMainDictionaryResourceId(Resources res) {
-        final String MAIN_DIC_NAME = "main";
-        String packageName = LatinIME.class.getPackage().getName();
-        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
-    }
-
-    public static void loadNativeLibrary() {
-        try {
-            System.loadLibrary("jni_latinime");
-        } catch (UnsatisfiedLinkError ule) {
-            Log.e(TAG, "Could not load native library jni_latinime");
-        }
-    }
-
-    /**
-     * Returns true if a and b are equal ignoring the case of the character.
-     * @param a first character to check
-     * @param b second character to check
-     * @return {@code true} if a and b are equal, {@code false} otherwise.
-     */
-    public static boolean equalsIgnoreCase(char a, char b) {
-        // Some language, such as Turkish, need testing both cases.
-        return a == b
-                || Character.toLowerCase(a) == Character.toLowerCase(b)
-                || Character.toUpperCase(a) == Character.toUpperCase(b);
-    }
-
-    /**
-     * Returns true if a and b are equal ignoring the case of the characters, including if they are
-     * both null.
-     * @param a first CharSequence to check
-     * @param b second CharSequence to check
-     * @return {@code true} if a and b are equal, {@code false} otherwise.
-     */
-    public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
-        if (a == b)
-            return true;  // including both a and b are null.
-        if (a == null || b == null)
-            return false;
-        final int length = a.length();
-        if (length != b.length())
-            return false;
-        for (int i = 0; i < length; i++) {
-            if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
-                return false;
-        }
-        return true;
-    }
-
-    /**
-     * Returns true if a and b are equal ignoring the case of the characters, including if a is null
-     * and b is zero length.
-     * @param a CharSequence to check
-     * @param b character array to check
-     * @param offset start offset of array b
-     * @param length length of characters in array b
-     * @return {@code true} if a and b are equal, {@code false} otherwise.
-     * @throws IndexOutOfBoundsException
-     *   if {@code offset < 0 || length < 0 || offset + length > data.length}.
-     * @throws NullPointerException if {@code b == null}.
-     */
-    public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
-        if (offset < 0 || length < 0 || length > b.length - offset)
-            throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
-                    + " length=" + length);
-        if (a == null)
-            return length == 0;  // including a is null and b is zero length.
-        if (a.length() != length)
-            return false;
-        for (int i = 0; i < length; i++) {
-            if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
-                return false;
-        }
-        return true;
-    }
-
     public static float getDipScale(Context context) {
         final float scale = context.getResources().getDisplayMetrics().density;
         return scale;
@@ -708,110 +431,56 @@
         return (int) (dip * scale + 0.5);
     }
 
-    /**
-     * Remove duplicates from an array of strings.
-     *
-     * This method will always keep the first occurence of all strings at their position
-     * in the array, removing the subsequent ones.
-     */
-    public static void removeDupes(final ArrayList<CharSequence> suggestions) {
-        if (suggestions.size() < 2) return;
-        int i = 1;
-        // Don't cache suggestions.size(), since we may be removing items
-        while (i < suggestions.size()) {
-            final CharSequence cur = suggestions.get(i);
-            // Compare each suggestion with each previous suggestion
-            for (int j = 0; j < i; j++) {
-                CharSequence previous = suggestions.get(j);
-                if (TextUtils.equals(cur, previous)) {
-                    removeFromSuggestions(suggestions, i);
-                    i--;
+    public static class Stats {
+        public static void onNonSeparator(final char code, final int x,
+                final int y) {
+            RingCharBuffer.getInstance().push(code, x, y);
+            LatinImeLogger.logOnInputChar();
+        }
+
+        public static void onSeparator(final int code, final int x,
+                final int y) {
+            // TODO: accept code points
+            RingCharBuffer.getInstance().push((char)code, x, y);
+            LatinImeLogger.logOnInputSeparator();
+        }
+
+        public static void onAutoCorrection(final String typedWord, final String correctedWord,
+                final int separatorCode) {
+            if (TextUtils.isEmpty(typedWord)) return;
+            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
+        }
+
+        public static void onAutoCorrectionCancellation() {
+            LatinImeLogger.logOnAutoCorrectionCancelled();
+        }
+    }
+
+    public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
+        if (!LatinImeLogger.sDBG) return null;
+        final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
+        if (wordInfo == null) return null;
+        final String info = wordInfo.getDebugString();
+        if (TextUtils.isEmpty(info)) return null;
+        return info;
+    }
+
+    private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
+    private static final HashMap<Integer, String> sDeviceOverrideValueMap =
+            new HashMap<Integer, String>();
+
+    public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
+        final Integer key = overrideResId;
+        if (!sDeviceOverrideValueMap.containsKey(key)) {
+            String overrideValue = defValue;
+            for (final String element : res.getStringArray(overrideResId)) {
+                if (element.startsWith(HARDWARE_PREFIX)) {
+                    overrideValue = element.substring(HARDWARE_PREFIX.length());
                     break;
                 }
             }
-            i++;
+            sDeviceOverrideValueMap.put(key, overrideValue);
         }
-    }
-
-    private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
-            final int index) {
-        final CharSequence garbage = suggestions.remove(index);
-        if (garbage instanceof StringBuilder) {
-            StringBuilderPool.recycle((StringBuilder)garbage);
-        }
-    }
-
-    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
-        if (returnsNameInThisLocale) {
-            return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
-        } else {
-            return toTitleCase(locale.getDisplayName(), locale);
-        }
-    }
-
-    public static String getDisplayLanguage(Locale locale) {
-        return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
-    }
-
-    public static String getMiddleDisplayLanguage(Locale locale) {
-        return toTitleCase((LocaleUtils.constructLocaleFromString(
-                locale.getLanguage()).getDisplayLanguage(locale)), locale);
-    }
-
-    public static String getShortDisplayLanguage(Locale locale) {
-        return toTitleCase(locale.getLanguage(), locale);
-    }
-
-    public static String toTitleCase(String s, Locale locale) {
-        if (s.length() <= 1) {
-            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
-            return s;
-        }
-        // TODO: fix the bugs below
-        // - This does not work for Greek, because it returns upper case instead of title case.
-        // - It does not work for Serbian, because it fails to account for the "lj" character,
-        // which should be "Lj" in title case and "LJ" in upper case.
-        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
-        // are two different characters but both should be capitalized as "IJ" as if they were
-        // a single letter.
-        // - It also does not work with unicode surrogate code points.
-        return s.toUpperCase(locale).charAt(0) + s.substring(1);
-    }
-
-    public static int getCurrentVibrationDuration(SharedPreferences sp, Resources res) {
-        final int ms = sp.getInt(Settings.PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS, -1);
-        if (ms >= 0) {
-            return ms;
-        }
-        final String[] durationPerHardwareList = res.getStringArray(
-                R.array.keypress_vibration_durations);
-        final String hardwarePrefix = Build.HARDWARE + ",";
-        for (final String element : durationPerHardwareList) {
-            if (element.startsWith(hardwarePrefix)) {
-                return (int)Long.parseLong(element.substring(element.lastIndexOf(',') + 1));
-            }
-        }
-        return -1;
-    }
-
-    public static float getCurrentKeypressSoundVolume(SharedPreferences sp, Resources res) {
-        final float volume = sp.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
-        if (volume >= 0) {
-            return volume;
-        }
-
-        final String[] volumePerHardwareList = res.getStringArray(R.array.keypress_volumes);
-        final String hardwarePrefix = Build.HARDWARE + ",";
-        for (final String element : volumePerHardwareList) {
-            if (element.startsWith(hardwarePrefix)) {
-                return Float.parseFloat(element.substring(element.lastIndexOf(',') + 1));
-            }
-        }
-        return -1.0f;
-    }
-
-    public static boolean willAutoCorrect(SuggestedWords suggestions) {
-        return !suggestions.mTypedWordValid && suggestions.mHasAutoCorrectionCandidate
-                && !suggestions.shouldBlockAutoCorrection();
+        return sDeviceOverrideValueMap.get(key);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/VibratorUtils.java b/java/src/com/android/inputmethod/latin/VibratorUtils.java
new file mode 100644
index 0000000..33ffdd9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/VibratorUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.os.Vibrator;
+
+public class VibratorUtils {
+    private static final VibratorUtils sInstance = new VibratorUtils();
+    private Vibrator mVibrator;
+
+    private VibratorUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static VibratorUtils getInstance(Context context) {
+        if (sInstance.mVibrator == null) {
+            sInstance.mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+        }
+        return sInstance;
+    }
+
+    public boolean hasVibrator() {
+        if (mVibrator == null) {
+            return false;
+        }
+        return mVibrator.hasVibrator();
+    }
+
+    public void vibrate(long milliseconds) {
+        if (mVibrator == null) {
+            return;
+        }
+        mVibrator.vibrate(milliseconds);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
index a90ef29..7bb3076 100644
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -22,6 +22,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -36,10 +38,14 @@
     // TODO: Conform to the async load contact of ExpandableDictionary
     public WhitelistDictionary(final Context context, final Locale locale) {
         super(context, Suggest.DIC_WHITELIST);
-        final Resources res = context.getResources();
-        final Locale previousLocale = LocaleUtils.setSystemLocale(res, locale);
-        initWordlist(res.getStringArray(R.array.wordlist_whitelist));
-        LocaleUtils.setSystemLocale(res, previousLocale);
+        final RunInLocale<Void> job = new RunInLocale<Void>() {
+            @Override
+            protected Void job(Resources res) {
+                initWordlist(res.getStringArray(R.array.wordlist_whitelist));
+                return null;
+            }
+        };
+        job.runInLocale(context.getResources(), locale);
     }
 
     private void initWordlist(String[] wordlist) {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index adc5637..bd8532e 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -1,12 +1,12 @@
 /*
  * Copyright (C) 2008 The Android Open Source Project
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -16,9 +16,12 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * A place to store the currently composing word with information such as adjacent key codes as well
@@ -28,31 +31,33 @@
     public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
     public static final int NOT_A_COORDINATE = -1;
 
-    /**
-     * The list of unicode values for each keystroke (including surrounding keys)
-     */
-    private ArrayList<int[]> mCodes;
+    private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
 
+    private int[] mPrimaryKeyCodes;
     private int[] mXCoordinates;
     private int[] mYCoordinates;
-
     private StringBuilder mTypedWord;
+    private CharSequence mAutoCorrection;
 
+    // Cache these values for performance
     private int mCapsCount;
-
     private boolean mAutoCapitalized;
-    
+    private int mTrailingSingleQuotesCount;
+    private int mCodePointSize;
+
     /**
      * Whether the user chose to capitalize the first char of the word.
      */
     private boolean mIsFirstCharCapitalized;
 
     public WordComposer() {
-        final int N = BinaryDictionary.MAX_WORD_LENGTH;
-        mCodes = new ArrayList<int[]>(N);
+        mPrimaryKeyCodes = new int[N];
         mTypedWord = new StringBuilder(N);
         mXCoordinates = new int[N];
         mYCoordinates = new int[N];
+        mAutoCorrection = null;
+        mTrailingSingleQuotesCount = 0;
+        refreshSize();
     }
 
     public WordComposer(WordComposer source) {
@@ -60,23 +65,31 @@
     }
 
     public void init(WordComposer source) {
-        mCodes = new ArrayList<int[]>(source.mCodes);
+        mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length);
         mTypedWord = new StringBuilder(source.mTypedWord);
-        mXCoordinates = source.mXCoordinates;
-        mYCoordinates = source.mYCoordinates;
+        mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
+        mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
         mCapsCount = source.mCapsCount;
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
+        mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
+        refreshSize();
     }
 
     /**
      * Clear out the keys registered so far.
      */
     public void reset() {
-        mCodes.clear();
         mTypedWord.setLength(0);
+        mAutoCorrection = null;
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
+        mTrailingSingleQuotesCount = 0;
+        refreshSize();
+    }
+
+    public final void refreshSize() {
+        mCodePointSize = mTypedWord.codePointCount(0, mTypedWord.length());
     }
 
     /**
@@ -84,16 +97,19 @@
      * @return the number of keystrokes
      */
     public final int size() {
-        return mTypedWord.length();
+        return mCodePointSize;
     }
 
-    /**
-     * Returns the codes at a particular position in the word.
-     * @param index the position in the word
-     * @return the unicode for the pressed and surrounding keys
-     */
-    public int[] getCodesAt(int index) {
-        return mCodes.get(index);
+    public final boolean isComposingWord() {
+        return size() > 0;
+    }
+
+    // TODO: make sure that the index should not exceed MAX_WORD_LENGTH
+    public int getCodeAt(int index) {
+        if (index >= BinaryDictionary.MAX_WORD_LENGTH) {
+            return -1;
+        }
+        return mPrimaryKeyCodes[index];
     }
 
     public int[] getXCoordinates() {
@@ -109,37 +125,73 @@
         return previous && !Character.isUpperCase(codePoint);
     }
 
+    // TODO: remove input keyDetector
+    public void add(int primaryCode, int x, int y, KeyDetector keyDetector) {
+        final int keyX;
+        final int keyY;
+        if (null == keyDetector
+                || x == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+                || y == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+                || x == KeyboardActionListener.NOT_A_TOUCH_COORDINATE
+                || y == KeyboardActionListener.NOT_A_TOUCH_COORDINATE) {
+            keyX = x;
+            keyY = y;
+        } else {
+            keyX = keyDetector.getTouchX(x);
+            keyY = keyDetector.getTouchY(y);
+        }
+        add(primaryCode, keyX, keyY);
+    }
+
     /**
-     * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
-     * the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
-     * @param codes the array of unicode values
+     * Add a new keystroke, with the pressed key's code point with the touch point coordinates.
      */
-    public void add(int primaryCode, int[] codes, int x, int y) {
+    private void add(int primaryCode, int keyX, int keyY) {
         final int newIndex = size();
-        mTypedWord.append((char) primaryCode);
-        correctPrimaryJuxtapos(primaryCode, codes);
-        mCodes.add(codes);
+        mTypedWord.appendCodePoint(primaryCode);
+        refreshSize();
         if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
-            mXCoordinates[newIndex] = x;
-            mYCoordinates[newIndex] = y;
+            mPrimaryKeyCodes[newIndex] = primaryCode >= Keyboard.CODE_SPACE
+                    ? Character.toLowerCase(primaryCode) : primaryCode;
+            mXCoordinates[newIndex] = keyX;
+            mYCoordinates[newIndex] = keyY;
         }
         mIsFirstCharCapitalized = isFirstCharCapitalized(
                 newIndex, primaryCode, mIsFirstCharCapitalized);
         if (Character.isUpperCase(primaryCode)) mCapsCount++;
+        if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+            ++mTrailingSingleQuotesCount;
+        } else {
+            mTrailingSingleQuotesCount = 0;
+        }
+        mAutoCorrection = null;
     }
 
     /**
-     * Swaps the first and second values in the codes array if the primary code is not the first
-     * value in the array but the second. This happens when the preferred key is not the key that
-     * the user released the finger on.
-     * @param primaryCode the preferred character
-     * @param codes array of codes based on distance from touch point
+     * Internal method to retrieve reasonable proximity info for a character.
      */
-    private void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
-        if (codes.length < 2) return;
-        if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) {
-            codes[1] = codes[0];
-            codes[0] = primaryCode;
+    private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
+        for (final Key key : keyboard.mKeys) {
+            if (key.mCode == codePoint) {
+                final int x = key.mX + key.mWidth / 2;
+                final int y = key.mY + key.mHeight / 2;
+                add(codePoint, x, y);
+                return;
+            }
+        }
+        add(codePoint, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+    }
+
+    /**
+     * Set the currently composing word to the one passed as an argument.
+     * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+     */
+    public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
+        reset();
+        final int length = word.length();
+        for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
+            int codePoint = Character.codePointAt(word, i);
+            addKeyInfo(codePoint, keyboard);
         }
     }
 
@@ -149,25 +201,43 @@
     public void deleteLast() {
         final int size = size();
         if (size > 0) {
-            final int lastPos = size - 1;
-            char lastChar = mTypedWord.charAt(lastPos);
-            mCodes.remove(lastPos);
-            mTypedWord.deleteCharAt(lastPos);
+            // Note: mTypedWord.length() and mCodes.length differ when there are surrogate pairs
+            final int stringBuilderLength = mTypedWord.length();
+            if (stringBuilderLength < size) {
+                throw new RuntimeException(
+                        "In WordComposer: mCodes and mTypedWords have non-matching lengths");
+            }
+            final int lastChar = mTypedWord.codePointBefore(stringBuilderLength);
+            if (Character.isSupplementaryCodePoint(lastChar)) {
+                mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength);
+            } else {
+                mTypedWord.deleteCharAt(stringBuilderLength - 1);
+            }
             if (Character.isUpperCase(lastChar)) mCapsCount--;
+            refreshSize();
         }
-        if (size() == 0) {
+        // We may have deleted the last one.
+        if (0 == size()) {
             mIsFirstCharCapitalized = false;
         }
+        if (mTrailingSingleQuotesCount > 0) {
+            --mTrailingSingleQuotesCount;
+        } else {
+            int i = mTypedWord.length();
+            while (i > 0) {
+                i = mTypedWord.offsetByCodePoints(i, -1);
+                if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+                ++mTrailingSingleQuotesCount;
+            }
+        }
+        mAutoCorrection = null;
     }
 
     /**
      * Returns the word as it was typed, without any correction applied.
-     * @return the word that was typed so far
+     * @return the word that was typed so far. Never returns null.
      */
     public String getTypedWord() {
-        if (size() == 0) {
-            return null;
-        }
         return mTypedWord.toString();
     }
 
@@ -179,6 +249,10 @@
         return mIsFirstCharCapitalized;
     }
 
+    public int trailingSingleQuotesCount() {
+        return mTrailingSingleQuotesCount;
+    }
+
     /**
      * Whether or not all of the user typed chars are upper case
      * @return true if all user typed chars are upper case, false otherwise
@@ -194,7 +268,7 @@
         return mCapsCount > 1;
     }
 
-    /** 
+    /**
      * Saves the reason why the word is capitalized - whether it was automatic or
      * due to the user hitting shift in the middle of a sentence.
      * @param auto whether it was an automatic capitalization due to start of sentence
@@ -210,4 +284,52 @@
     public boolean isAutoCapitalized() {
         return mAutoCapitalized;
     }
+
+    /**
+     * Sets the auto-correction for this word.
+     */
+    public void setAutoCorrection(final CharSequence correction) {
+        mAutoCorrection = correction;
+    }
+
+    /**
+     * @return the auto-correction for this word, or null if none.
+     */
+    public CharSequence getAutoCorrectionOrNull() {
+        return mAutoCorrection;
+    }
+
+    // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
+    public LastComposedWord commitWord(final int type, final String committedWord,
+            final int separatorCode) {
+        // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
+        // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
+        // the last composed word to ensure this does not happen.
+        final int[] primaryKeyCodes = mPrimaryKeyCodes;
+        final int[] xCoordinates = mXCoordinates;
+        final int[] yCoordinates = mYCoordinates;
+        mPrimaryKeyCodes = new int[N];
+        mXCoordinates = new int[N];
+        mYCoordinates = new int[N];
+        final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
+                xCoordinates, yCoordinates, mTypedWord.toString(), committedWord, separatorCode);
+        if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
+                && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
+            lastComposedWord.deactivate();
+        }
+        mTypedWord.setLength(0);
+        refreshSize();
+        mAutoCorrection = null;
+        return lastComposedWord;
+    }
+
+    public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
+        mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
+        mXCoordinates = lastComposedWord.mXCoordinates;
+        mYCoordinates = lastComposedWord.mYCoordinates;
+        mTypedWord.setLength(0);
+        mTypedWord.append(lastComposedWord.mTypedWord);
+        refreshSize();
+        mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
new file mode 100644
index 0000000..481cdfa
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.res.TypedArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public class XmlParseUtils {
+    private XmlParseUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    @SuppressWarnings("serial")
+    public static class ParseException extends XmlPullParserException {
+        public ParseException(String msg, XmlPullParser parser) {
+            super(msg + " at " + parser.getPositionDescription());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class IllegalStartTag extends ParseException {
+        public IllegalStartTag(XmlPullParser parser, String parent) {
+            super("Illegal start tag " + parser.getName() + " in " + parent, parser);
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class IllegalEndTag extends ParseException {
+        public IllegalEndTag(XmlPullParser parser, String parent) {
+            super("Illegal end tag " + parser.getName() + " in " + parent, parser);
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class IllegalAttribute extends ParseException {
+        public IllegalAttribute(XmlPullParser parser, String attribute) {
+            super("Tag " + parser.getName() + " has illegal attribute " + attribute, parser);
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class NonEmptyTag extends ParseException{
+        public NonEmptyTag(String tag, XmlPullParser parser) {
+            super(tag + " must be empty tag", parser);
+        }
+    }
+
+    public static void checkEndTag(String tag, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (parser.next() == XmlPullParser.END_TAG && tag.equals(parser.getName()))
+            return;
+        throw new NonEmptyTag(tag, parser);
+    }
+
+    public static void checkAttributeExists(TypedArray attr, int attrId, String attrName,
+            String tag, XmlPullParser parser) throws XmlPullParserException {
+        if (attr.hasValue(attrId))
+            return;
+        throw new ParseException(
+                "No " + attrName + " attribute found in <" + tag + "/>", parser);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java
similarity index 70%
copy from tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
copy to java/src/com/android/inputmethod/latin/define/JniLibName.java
index 511ad56..e23e1a9 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/define/JniLibName.java
@@ -14,13 +14,12 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.define;
 
-/**
- * Simple exception thrown when a file format is not recognized.
- */
-public class UnsupportedFormatException extends Exception {
-    public UnsupportedFormatException(String description) {
-        super(description);
+public final class JniLibName {
+    private JniLibName() {
+        // This class is not publicly instantiable.
     }
+
+    public static final String JNI_LIB_NAME = "jni_latinime";
 }
diff --git a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
similarity index 64%
copy from tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
copy to java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 511ad56..de20576 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -14,13 +14,12 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.define;
 
-/**
- * Simple exception thrown when a file format is not recognized.
- */
-public class UnsupportedFormatException extends Exception {
-    public UnsupportedFormatException(String description) {
-        super(description);
+public final class ProductionFlag {
+    private ProductionFlag() {
+        // This class is not publicly instantiable.
     }
+
+    public static final boolean IS_EXPERIMENTAL = false;
 }
diff --git a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
similarity index 70%
rename from tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
rename to java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 92f402d..010ea68 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -14,11 +14,11 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
-import com.android.inputmethod.latin.FusionDictionary.CharGroup;
-import com.android.inputmethod.latin.FusionDictionary.Node;
-import com.android.inputmethod.latin.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -26,6 +26,8 @@
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.TreeMap;
 
@@ -44,7 +46,7 @@
      * a |                                     11 = 3 bytes     : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
      * g | has several chars ?         1 bit, 1 = yes, 0 = no   : FLAG_HAS_MULTIPLE_CHARS
      * s | has a terminal ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_TERMINAL
-     *   | reserved                    1 bit, 1 = yes, 0 = no
+     *   | has shortcut targets ?      1 bit, 1 = yes, 0 = no   : FLAG_HAS_SHORTCUT_TARGETS
      *   | has bigrams ?               1 bit, 1 = yes, 0 = no   : FLAG_HAS_BIGRAMS
      *
      * c | IF FLAG_HAS_MULTIPLE_CHARS
@@ -71,6 +73,8 @@
      * d
      * dress
      *
+     *   | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
+     *   | shortcut string list
      *   | IF FLAG_IS_TERMINAL && FLAG_HAS_BIGRAMS
      *   | bigrams address list
      *
@@ -85,7 +89,7 @@
      * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
      * characters which should never happen anyway (and still work, but take 3 bytes).
      *
-     * bigram and shortcut address list is:
+     * bigram address list is:
      * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no     : FLAG_ATTRIBUTE_HAS_NEXT
      *           | addressSign = 1 bit,                 : FLAG_ATTRIBUTE_OFFSET_NEGATIVE
      *           |                      1 = must take -address, 0 = must take +address
@@ -103,13 +107,25 @@
      *           |   read 3 bytes, add top 4 bits
      *           | END
      *           | if (FLAG_ATTRIBUTE_OFFSET_NEGATIVE) then address = -address
-     * if (FLAG_ATTRIBUTE_HAS_NET) goto bigram_and_shortcut_address_list_is
+     * if (FLAG_ATTRIBUTE_HAS_NEXT) goto bigram_and_shortcut_address_list_is
      *
+     * shortcut string list is:
+     * <byte size> = GROUP_SHORTCUT_LIST_SIZE_SIZE bytes, big-endian: size of the list, in bytes.
+     * <flags>     = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_ATTRIBUTE_HAS_NEXT
+     *               | reserved = 3 bits, must be 0
+     *               | 4 bits : frequency : mask with FLAG_ATTRIBUTE_FREQUENCY
+     * <shortcut>  = | string of characters at the char format described above, with the terminator
+     *               | used to signal the end of the string.
+     * if (FLAG_ATTRIBUTE_HAS_NEXT goto flags
      */
 
-    private static final int MAGIC_NUMBER = 0x78B1;
-    private static final int VERSION = 1;
-    private static final int MAXIMUM_SUPPORTED_VERSION = VERSION;
+    private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
+    private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+    private static final int MINIMUM_SUPPORTED_VERSION = 1;
+    private static final int MAXIMUM_SUPPORTED_VERSION = 2;
+    private static final int NOT_A_VERSION_NUMBER = -1;
+    private static final int FIRST_VERSION_WITH_HEADER_SIZE = 2;
+
     // No options yet, reserved for future use.
     private static final int OPTIONS = 0;
 
@@ -126,6 +142,7 @@
     private static final int FLAG_HAS_MULTIPLE_CHARS = 0x20;
 
     private static final int FLAG_IS_TERMINAL = 0x10;
+    private static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
     private static final int FLAG_HAS_BIGRAMS = 0x04;
 
     private static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
@@ -138,20 +155,19 @@
 
     private static final int GROUP_CHARACTERS_TERMINATOR = 0x1F;
 
-    private static final int GROUP_COUNT_SIZE = 1;
     private static final int GROUP_TERMINATOR_SIZE = 1;
     private static final int GROUP_FLAGS_SIZE = 1;
     private static final int GROUP_FREQUENCY_SIZE = 1;
     private static final int GROUP_MAX_ADDRESS_SIZE = 3;
     private static final int GROUP_ATTRIBUTE_FLAGS_SIZE = 1;
     private static final int GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
+    private static final int GROUP_SHORTCUT_LIST_SIZE_SIZE = 2;
 
     private static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
     private static final int INVALID_CHARACTER = -1;
 
-    // Limiting to 127 for upward compatibility
-    // TODO: implement a scheme to be able to shoot 256 chargroups in a node
-    private static final int MAX_CHARGROUPS_IN_A_NODE = 127;
+    private static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127
+    private static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767
 
     private static final int MAX_TERMINAL_FREQUENCY = 255;
 
@@ -207,25 +223,66 @@
         /**
          * Writes a char array to a byte buffer.
          *
-         * @param characters the character array to write.
+         * @param codePoints the code point array to write.
          * @param buffer the byte buffer to write to.
          * @param index the index in buffer to write the character array to.
          * @return the index after the last character.
          */
-        private static int writeCharArray(int[] characters, byte[] buffer, int index) {
-            for (int character : characters) {
-                if (1 == getCharSize(character)) {
-                    buffer[index++] = (byte)character;
+        private static int writeCharArray(final int[] codePoints, final byte[] buffer, int index) {
+            for (int codePoint : codePoints) {
+                if (1 == getCharSize(codePoint)) {
+                    buffer[index++] = (byte)codePoint;
                 } else {
-                    buffer[index++] = (byte)(0xFF & (character >> 16));
-                    buffer[index++] = (byte)(0xFF & (character >> 8));
-                    buffer[index++] = (byte)(0xFF & character);
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 16));
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 8));
+                    buffer[index++] = (byte)(0xFF & codePoint);
                 }
             }
             return index;
         }
 
         /**
+         * Writes a string with our character format to a byte buffer.
+         *
+         * This will also write the terminator byte.
+         *
+         * @param buffer the byte buffer to write to.
+         * @param origin the offset to write from.
+         * @param word the string to write.
+         * @return the size written, in bytes.
+         */
+        private static int writeString(final byte[] buffer, final int origin,
+                final String word) {
+            final int length = word.length();
+            int index = origin;
+            for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+                final int codePoint = word.codePointAt(i);
+                if (1 == getCharSize(codePoint)) {
+                    buffer[index++] = (byte)codePoint;
+                } else {
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 16));
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 8));
+                    buffer[index++] = (byte)(0xFF & codePoint);
+                }
+            }
+            buffer[index++] = GROUP_CHARACTERS_TERMINATOR;
+            return index - origin;
+        }
+
+        /**
+         * Reads a string from a RandomAccessFile. This is the converse of the above method.
+         */
+        private static String readString(final RandomAccessFile source) throws IOException {
+            final StringBuilder s = new StringBuilder();
+            int character = readChar(source);
+            while (character != INVALID_CHARACTER) {
+                s.appendCodePoint(character);
+                character = readChar(source);
+            }
+            return s.toString();
+        }
+
+        /**
          * Reads a character from the file.
          *
          * This follows the character format documented earlier in this source file.
@@ -261,6 +318,61 @@
     }
 
     /**
+     * Compute the binary size of the group count
+     * @param count the group count
+     * @return the size of the group count, either 1 or 2 bytes.
+     */
+    private static int getGroupCountSize(final int count) {
+        if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= count) {
+            return 1;
+        } else if (MAX_CHARGROUPS_IN_A_NODE >= count) {
+            return 2;
+        } else {
+            throw new RuntimeException("Can't have more than " + MAX_CHARGROUPS_IN_A_NODE
+                    + " groups in a node (found " + count +")");
+        }
+    }
+
+    /**
+     * Compute the binary size of the group count for a node
+     * @param node the node
+     * @return the size of the group count, either 1 or 2 bytes.
+     */
+    private static int getGroupCountSize(final Node node) {
+        return getGroupCountSize(node.mData.size());
+    }
+
+    /**
+     * Compute the size of a shortcut in bytes.
+     */
+    private static int getShortcutSize(final WeightedString shortcut) {
+        int size = GROUP_ATTRIBUTE_FLAGS_SIZE;
+        final String word = shortcut.mWord;
+        final int length = word.length();
+        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+            final int codePoint = word.codePointAt(i);
+            size += CharEncoding.getCharSize(codePoint);
+        }
+        size += GROUP_TERMINATOR_SIZE;
+        return size;
+    }
+
+    /**
+     * Compute the size of a shortcut list in bytes.
+     *
+     * This is known in advance and does not change according to position in the file
+     * like address lists do.
+     */
+    private static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
+        if (null == shortcutList) return 0;
+        int size = GROUP_SHORTCUT_LIST_SIZE_SIZE;
+        for (final WeightedString shortcut : shortcutList) {
+            size += getShortcutSize(shortcut);
+        }
+        return size;
+    }
+
+    /**
      * Compute the maximum size of a CharGroup, assuming 3-byte addresses for everything.
      *
      * @param group the CharGroup to compute the size of.
@@ -271,10 +383,10 @@
         // If terminal, one byte for the frequency
         if (group.isTerminal()) size += GROUP_FREQUENCY_SIZE;
         size += GROUP_MAX_ADDRESS_SIZE; // For children address
+        size += getShortcutListSize(group.mShortcutTargets);
         if (null != group.mBigrams) {
-            for (WeightedString bigram : group.mBigrams) {
-                size += GROUP_ATTRIBUTE_FLAGS_SIZE + GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE;
-            }
+            size += (GROUP_ATTRIBUTE_FLAGS_SIZE + GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE)
+                    * group.mBigrams.size();
         }
         return size;
     }
@@ -286,7 +398,7 @@
      * @param node the node to compute the maximum size of.
      */
     private static void setNodeMaximumSize(Node node) {
-        int size = GROUP_COUNT_SIZE;
+        int size = getGroupCountSize(node);
         for (CharGroup g : node.mData) {
             final int groupSize = getCharGroupMaximumSize(g);
             g.mCachedSize = groupSize;
@@ -378,7 +490,7 @@
      * @param dict the dictionary in which the word/attributes are to be found.
      */
     private static void computeActualNodeSize(Node node, FusionDictionary dict) {
-        int size = GROUP_COUNT_SIZE;
+        int size = getGroupCountSize(node);
         for (CharGroup group : node.mData) {
             int groupSize = GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
             if (group.isTerminal()) groupSize += GROUP_FREQUENCY_SIZE;
@@ -387,6 +499,7 @@
                 final int offset = group.mChildren.mCachedAddress - offsetBasePoint;
                 groupSize += getByteSize(offset);
             }
+            groupSize += getShortcutListSize(group.mShortcutTargets);
             if (null != group.mBigrams) {
                 for (WeightedString bigram : group.mBigrams) {
                     final int offsetBasePoint = groupSize + node.mCachedAddress + size
@@ -412,12 +525,13 @@
         int nodeOffset = 0;
         for (Node n : flatNodes) {
             n.mCachedAddress = nodeOffset;
+            int groupCountSize = getGroupCountSize(n);
             int groupOffset = 0;
             for (CharGroup g : n.mData) {
-                g.mCachedAddress = GROUP_COUNT_SIZE + nodeOffset + groupOffset;
+                g.mCachedAddress = groupCountSize + nodeOffset + groupOffset;
                 groupOffset += g.mCachedSize;
             }
-            if (groupOffset + GROUP_COUNT_SIZE != n.mCachedSize) {
+            if (groupOffset + groupCountSize != n.mCachedSize) {
                 throw new RuntimeException("Bug : Stored and computed node size differ");
             }
             nodeOffset += n.mCachedSize;
@@ -502,7 +616,7 @@
      * @param address the address to write.
      * @return the size in bytes the address actually took.
      */
-    private static int writeVariableAddress(byte[] buffer, int index, int address) {
+    private static int writeVariableAddress(final byte[] buffer, int index, final int address) {
         switch (getByteSize(address)) {
         case 1:
             buffer[index++] = (byte)address;
@@ -545,7 +659,18 @@
                  throw new RuntimeException("Node with a strange address");
              }
         }
-        if (null != group.mBigrams) flags |= FLAG_HAS_BIGRAMS;
+        if (null != group.mShortcutTargets) {
+            if (0 == group.mShortcutTargets.size()) {
+                throw new RuntimeException("0-sized shortcut list must be null");
+            }
+            flags |= FLAG_HAS_SHORTCUT_TARGETS;
+        }
+        if (null != group.mBigrams) {
+            if (0 == group.mBigrams.size()) {
+                throw new RuntimeException("0-sized bigram list must be null");
+            }
+            flags |= FLAG_HAS_BIGRAMS;
+        }
         return flags;
     }
 
@@ -579,6 +704,17 @@
     }
 
     /**
+     * Makes the flag value for a shortcut.
+     *
+     * @param more whether there are more attributes after this one.
+     * @param frequency the frequency of the attribute, 0..15
+     * @return the flags
+     */
+    private static final int makeShortcutFlags(final boolean more, final int frequency) {
+        return (more ? FLAG_ATTRIBUTE_HAS_NEXT : 0) + (frequency & FLAG_ATTRIBUTE_FREQUENCY);
+    }
+
+    /**
      * Write a node to memory. The node is expected to have its final position cached.
      *
      * This can be an empty map, but the more is inside the faster the lookups will be. It can
@@ -592,16 +728,24 @@
     private static int writePlacedNode(FusionDictionary dict, byte[] buffer, Node node) {
         int index = node.mCachedAddress;
 
-        final int size = node.mData.size();
-        if (size > MAX_CHARGROUPS_IN_A_NODE)
-            throw new RuntimeException("A node has a group count over 127 (" + size + ").");
-
-        buffer[index++] = (byte)size;
+        final int groupCount = node.mData.size();
+        final int countSize = getGroupCountSize(node);
+        if (1 == countSize) {
+            buffer[index++] = (byte)groupCount;
+        } else if (2 == countSize) {
+            // We need to signal 2-byte size by setting the top bit of the MSB to 1, so
+            // we | 0x80 to do this.
+            buffer[index++] = (byte)((groupCount >> 8) | 0x80);
+            buffer[index++] = (byte)(groupCount & 0xFF);
+        } else {
+            throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
+        }
         int groupAddress = index;
-        for (int i = 0; i < size; ++i) {
+        for (int i = 0; i < groupCount; ++i) {
             CharGroup group = node.mData.get(i);
             if (index != group.mCachedAddress) throw new RuntimeException("Bug: write index is not "
-                    + "the same as the cached address of the group");
+                    + "the same as the cached address of the group : "
+                    + index + " <> " + group.mCachedAddress);
             groupAddress += GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
             // Sanity checks.
             if (group.mFrequency > MAX_TERMINAL_FREQUENCY) {
@@ -624,20 +768,43 @@
             index += shift;
             groupAddress += shift;
 
+            // Write shortcuts
+            if (null != group.mShortcutTargets) {
+                final int indexOfShortcutByteSize = index;
+                index += GROUP_SHORTCUT_LIST_SIZE_SIZE;
+                groupAddress += GROUP_SHORTCUT_LIST_SIZE_SIZE;
+                final Iterator shortcutIterator = group.mShortcutTargets.iterator();
+                while (shortcutIterator.hasNext()) {
+                    final WeightedString target = (WeightedString)shortcutIterator.next();
+                    ++groupAddress;
+                    int shortcutFlags = makeShortcutFlags(shortcutIterator.hasNext(),
+                            target.mFrequency);
+                    buffer[index++] = (byte)shortcutFlags;
+                    final int shortcutShift = CharEncoding.writeString(buffer, index, target.mWord);
+                    index += shortcutShift;
+                    groupAddress += shortcutShift;
+                }
+                final int shortcutByteSize = index - indexOfShortcutByteSize;
+                if (shortcutByteSize > 0xFFFF) {
+                    throw new RuntimeException("Shortcut list too large");
+                }
+                buffer[indexOfShortcutByteSize] = (byte)(shortcutByteSize >> 8);
+                buffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF);
+            }
             // Write bigrams
             if (null != group.mBigrams) {
-                int remainingBigrams = group.mBigrams.size();
-                for (WeightedString bigram : group.mBigrams) {
-                    boolean more = remainingBigrams > 1;
+                final Iterator bigramIterator = group.mBigrams.iterator();
+                while (bigramIterator.hasNext()) {
+                    final WeightedString bigram = (WeightedString)bigramIterator.next();
                     final int addressOfBigram = findAddressOfWord(dict, bigram.mWord);
                     ++groupAddress;
                     final int offset = addressOfBigram - groupAddress;
-                    int bigramFlags = makeAttributeFlags(more, offset, bigram.mFrequency);
+                    int bigramFlags = makeAttributeFlags(bigramIterator.hasNext(), offset,
+                            bigram.mFrequency);
                     buffer[index++] = (byte)bigramFlags;
                     final int bigramShift = writeVariableAddress(buffer, index, Math.abs(offset));
                     index += bigramShift;
                     groupAddress += bigramShift;
-                    --remainingBigrams;
                 }
             }
 
@@ -711,9 +878,11 @@
      *
      * @param destination the stream to write the binary data to.
      * @param dict the dictionary to write.
+     * @param version the version of the format to write, currently either 1 or 2.
      */
-    public static void writeDictionaryBinary(OutputStream destination, FusionDictionary dict)
-            throws IOException {
+    public static void writeDictionaryBinary(final OutputStream destination,
+            final FusionDictionary dict, final int version)
+            throws IOException, UnsupportedFormatException {
 
         // Addresses are limited to 3 bytes, so we'll just make a 16MB buffer. Since addresses
         // can be relative to each node, the structure itself is not limited to 16MB at all, but
@@ -725,16 +894,49 @@
         final byte[] buffer = new byte[1 << 24];
         int index = 0;
 
-        // Magic number in big-endian order.
-        buffer[index++] = (byte) (0xFF & (MAGIC_NUMBER >> 8));
-        buffer[index++] = (byte) (0xFF & MAGIC_NUMBER);
-        // Dictionary version.
-        buffer[index++] = (byte) (0xFF & VERSION);
+        if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
+            throw new UnsupportedFormatException("Requested file format version " + version
+                    + ", but this implementation only supports versions "
+                    + MINIMUM_SUPPORTED_VERSION + " through " + MAXIMUM_SUPPORTED_VERSION);
+        }
+
+        // The magic number in big-endian order.
+        if (version >= FIRST_VERSION_WITH_HEADER_SIZE) {
+            // Magic number for version 2+.
+            buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 24));
+            buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 16));
+            buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 8));
+            buffer[index++] = (byte) (0xFF & VERSION_2_MAGIC_NUMBER);
+            // Dictionary version.
+            buffer[index++] = (byte) (0xFF & (version >> 8));
+            buffer[index++] = (byte) (0xFF & version);
+        } else {
+            // Magic number for version 1.
+            buffer[index++] = (byte) (0xFF & (VERSION_1_MAGIC_NUMBER >> 8));
+            buffer[index++] = (byte) (0xFF & VERSION_1_MAGIC_NUMBER);
+            // Dictionary version.
+            buffer[index++] = (byte) (0xFF & version);
+        }
         // Options flags
         buffer[index++] = (byte) (0xFF & (OPTIONS >> 8));
         buffer[index++] = (byte) (0xFF & OPTIONS);
+        if (version >= FIRST_VERSION_WITH_HEADER_SIZE) {
+            final int headerSizeOffset = index;
+            index += 4; // Size of the header size
 
-        // Should we include the locale and title of the dictionary ?
+            // Write out the options.
+            for (final String key : dict.mOptions.mAttributes.keySet()) {
+                final String value = dict.mOptions.mAttributes.get(key);
+                index += CharEncoding.writeString(buffer, index, key);
+                index += CharEncoding.writeString(buffer, index, value);
+            }
+
+            // Write out the header size.
+            buffer[headerSizeOffset] = (byte) (0xFF & (index >> 24));
+            buffer[headerSizeOffset + 1] = (byte) (0xFF & (index >> 16));
+            buffer[headerSizeOffset + 2] = (byte) (0xFF & (index >> 8));
+            buffer[headerSizeOffset + 3] = (byte) (0xFF & (index >> 0));
+        }
 
         destination.write(buffer, 0, index);
         index = 0;
@@ -814,14 +1016,26 @@
             childrenAddress = NO_CHILDREN_ADDRESS;
             break;
         }
+        ArrayList<WeightedString> shortcutTargets = null;
+        if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
+            final long pointerBefore = source.getFilePointer();
+            shortcutTargets = new ArrayList<WeightedString>();
+            source.readUnsignedShort(); // Skip the size
+            while (true) {
+                final int targetFlags = source.readUnsignedByte();
+                final String word = CharEncoding.readString(source);
+                shortcutTargets.add(new WeightedString(word,
+                        targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
+                if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
+            }
+            addressPointer += (source.getFilePointer() - pointerBefore);
+        }
         ArrayList<PendingAttribute> bigrams = null;
         if (0 != (flags & FLAG_HAS_BIGRAMS)) {
             bigrams = new ArrayList<PendingAttribute>();
-            boolean more = true;
-            while (more) {
-                int bigramFlags = source.readUnsignedByte();
+            while (true) {
+                final int bigramFlags = source.readUnsignedByte();
                 ++addressPointer;
-                more = (0 != (bigramFlags & FLAG_ATTRIBUTE_HAS_NEXT));
                 final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
                 int bigramAddress = addressPointer;
                 switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
@@ -840,14 +1054,28 @@
                     addressPointer += 3;
                     break;
                 default:
-                    throw new RuntimeException("Has attribute with no address");
+                    throw new RuntimeException("Has bigrams with no address");
                 }
                 bigrams.add(new PendingAttribute(bigramFlags & FLAG_ATTRIBUTE_FREQUENCY,
                         bigramAddress));
+                if (0 == (bigramFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
             }
         }
         return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency,
-                childrenAddress, bigrams);
+                childrenAddress, shortcutTargets, bigrams);
+    }
+
+    /**
+     * Reads and returns the char group count out of a file and forwards the pointer.
+     */
+    private static int readCharGroupCount(RandomAccessFile source) throws IOException {
+        final int msb = source.readUnsignedByte();
+        if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
+            return msb;
+        } else {
+            return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
+                    + source.readUnsignedByte();
+        }
     }
 
     /**
@@ -863,8 +1091,8 @@
             int address) throws IOException {
         final long originalPointer = source.getFilePointer();
         source.seek(headerSize);
-        final int count = source.readUnsignedByte();
-        int groupOffset = 1; // 1 for the group count
+        final int count = readCharGroupCount(source);
+        int groupOffset = getGroupCountSize(count);
         final StringBuilder builder = new StringBuilder();
         String result = null;
 
@@ -920,11 +1148,12 @@
             Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
             throws IOException {
         final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
-        final int count = source.readUnsignedByte();
+        final int count = readCharGroupCount(source);
         final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
-        int groupOffset = nodeOrigin + 1; // 1 byte for the group count
+        int groupOffset = nodeOrigin + getGroupCountSize(count);
         for (int i = count; i > 0; --i) {
             CharGroupInfo info = readCharGroup(source, groupOffset);
+            ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
             ArrayList<WeightedString> bigrams = null;
             if (null != info.mBigrams) {
                 bigrams = new ArrayList<WeightedString>();
@@ -942,11 +1171,12 @@
                     source.seek(currentPosition);
                 }
                 nodeContents.add(
-                        new CharGroup(info.mCharacters, bigrams, info.mFrequency,
-                        children));
+                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
+                                children, false));
             } else {
                 nodeContents.add(
-                        new CharGroup(info.mCharacters, bigrams, info.mFrequency));
+                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
+                                false));
             }
             groupOffset = info.mEndAddress;
         }
@@ -957,6 +1187,17 @@
     }
 
     /**
+     * Helper function to get the binary format version from the header.
+     */
+    private static int getFormatVersion(final RandomAccessFile source) throws IOException {
+        final int magic_v1 = source.readUnsignedShort();
+        if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
+        final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
+        if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
+        return NOT_A_VERSION_NUMBER;
+    }
+
+    /**
      * Reads a random access file and returns the memory representation of the dictionary.
      *
      * This high-level method takes a binary file and reads its contents, populating a
@@ -967,18 +1208,11 @@
      * @param dict an optional dictionary to add words to, or null.
      * @return the created (or merged) dictionary.
      */
-    public static FusionDictionary readDictionaryBinary(RandomAccessFile source,
-            FusionDictionary dict) throws IOException, UnsupportedFormatException {
-        // Check magic number
-        final int magic = source.readUnsignedShort();
-        if (MAGIC_NUMBER != magic) {
-            throw new UnsupportedFormatException("The magic number in this file does not match "
-                    + "the expected value");
-        }
-
+    public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
+            final FusionDictionary dict) throws IOException, UnsupportedFormatException {
         // Check file version
-        final int version = source.readUnsignedByte();
-        if (version > MAXIMUM_SUPPORTED_VERSION) {
+        final int version = getFormatVersion(source);
+        if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
             throw new UnsupportedFormatException("This file has version " + version
                     + ", but this implementation does not support versions above "
                     + MAXIMUM_SUPPORTED_VERSION);
@@ -987,16 +1221,30 @@
         // Read options
         source.readUnsignedShort();
 
-        long headerSize = source.getFilePointer();
+        final long headerSize;
+        final HashMap<String, String> options = new HashMap<String, String>();
+        if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
+            headerSize = source.getFilePointer();
+        } else {
+            headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
+                    + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
+            while (source.getFilePointer() < headerSize) {
+                final String key = CharEncoding.readString(source);
+                final String value = CharEncoding.readString(source);
+                options.put(key, value);
+            }
+            source.seek(headerSize);
+        }
+
         Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
         Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
         final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
 
         FusionDictionary newDict = new FusionDictionary(root,
-                new FusionDictionary.DictionaryOptions());
+                new FusionDictionary.DictionaryOptions(options));
         if (null != dict) {
             for (Word w : dict) {
-                newDict.add(w.mWord, w.mFrequency, w.mBigrams);
+                newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mBigrams);
             }
         }
 
@@ -1011,10 +1259,11 @@
      * @param filename The name of the file to test.
      * @return true if it's a binary dictionary, false otherwise
      */
-    public static boolean isBinaryDictionary(String filename) {
+    public static boolean isBinaryDictionary(final String filename) {
         try {
             RandomAccessFile f = new RandomAccessFile(filename, "r");
-            return MAGIC_NUMBER == f.readUnsignedShort();
+            final int version = getFormatVersion(f);
+            return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
         } catch (FileNotFoundException e) {
             return false;
         } catch (IOException e) {
diff --git a/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
similarity index 83%
rename from tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
rename to java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
index 6badfd1..ef7dbb2 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
@@ -14,7 +14,9 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import java.util.ArrayList;
 
@@ -29,10 +31,12 @@
     public final int[] mCharacters;
     public final int mFrequency;
     public final int mChildrenAddress;
+    public final ArrayList<WeightedString> mShortcutTargets;
     public final ArrayList<PendingAttribute> mBigrams;
 
     public CharGroupInfo(final int originalAddress, final int endAddress, final int flags,
             final int[] characters, final int frequency, final int childrenAddress,
+            final ArrayList<WeightedString> shortcutTargets,
             final ArrayList<PendingAttribute> bigrams) {
         mOriginalAddress = originalAddress;
         mEndAddress = endAddress;
@@ -40,6 +44,7 @@
         mCharacters = characters;
         mFrequency = frequency;
         mChildrenAddress = childrenAddress;
+        mShortcutTargets = shortcutTargets;
         mBigrams = bigrams;
     }
 }
diff --git a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
similarity index 65%
rename from tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
rename to java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index f6220ee..99b1704 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -14,14 +14,14 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 
 /**
  * A dictionary that can fusion heads and tails of words for more compression.
@@ -60,15 +60,28 @@
      */
     public static class WeightedString {
         final String mWord;
-        final int mFrequency;
+        int mFrequency;
         public WeightedString(String word, int frequency) {
             mWord = word;
             mFrequency = frequency;
         }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(new Object[] { mWord, mFrequency });
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) return true;
+            if (!(o instanceof WeightedString)) return false;
+            WeightedString w = (WeightedString)o;
+            return mWord.equals(w.mWord) && mFrequency == w.mFrequency;
+        }
     }
 
     /**
-     * A group of characters, with a frequency, shortcuts, bigrams, and children.
+     * A group of characters, with a frequency, shortcut targets, bigrams, and children.
      *
      * This is the central class of the in-memory representation. A CharGroup is what can
      * be seen as a traditional "trie node", except it can hold several characters at the
@@ -82,25 +95,39 @@
     public static class CharGroup {
         public static final int NOT_A_TERMINAL = -1;
         final int mChars[];
-        final ArrayList<WeightedString> mBigrams;
-        final int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
+        ArrayList<WeightedString> mShortcutTargets;
+        ArrayList<WeightedString> mBigrams;
+        int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
+        boolean mIsShortcutOnly; // Only valid if this is a terminal.
         Node mChildren;
         // The two following members to help with binary generation
         int mCachedSize;
         int mCachedAddress;
 
-        public CharGroup(final int[] chars,
-                final ArrayList<WeightedString> bigrams, final int frequency) {
+        public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
+                final ArrayList<WeightedString> bigrams, final int frequency,
+                final boolean isShortcutOnly) {
             mChars = chars;
             mFrequency = frequency;
+            mIsShortcutOnly = isShortcutOnly;
+            if (mIsShortcutOnly && NOT_A_TERMINAL == mFrequency) {
+                throw new RuntimeException("A node must be a terminal to be a shortcut only");
+            }
+            mShortcutTargets = shortcutTargets;
             mBigrams = bigrams;
             mChildren = null;
         }
 
-        public CharGroup(final int[] chars,
-                final ArrayList<WeightedString> bigrams, final int frequency, final Node children) {
+        public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
+                final ArrayList<WeightedString> bigrams, final int frequency, final Node children,
+                final boolean isShortcutOnly) {
             mChars = chars;
             mFrequency = frequency;
+            mIsShortcutOnly = isShortcutOnly;
+            if (mIsShortcutOnly && NOT_A_TERMINAL == mFrequency) {
+                throw new RuntimeException("A node must be a terminal to be a shortcut only");
+            }
+            mShortcutTargets = shortcutTargets;
             mBigrams = bigrams;
             mChildren = children;
         }
@@ -120,6 +147,102 @@
             assert(mChars.length > 0);
             return 1 < mChars.length;
         }
+
+        /**
+         * Adds a word to the bigram list. Updates the frequency if the word already
+         * exists.
+         */
+        public void addBigram(final String word, final int frequency) {
+            if (mBigrams == null) {
+                mBigrams = new ArrayList<WeightedString>();
+            }
+            WeightedString bigram = getBigram(word);
+            if (bigram != null) {
+                bigram.mFrequency = frequency;
+            } else {
+                bigram = new WeightedString(word, frequency);
+                mBigrams.add(bigram);
+            }
+        }
+
+        /**
+         * Gets the shortcut target for the given word. Returns null if the word is not in the
+         * shortcut list.
+         */
+        public WeightedString getShortcut(final String word) {
+            if (mShortcutTargets != null) {
+                final int size = mShortcutTargets.size();
+                for (int i = 0; i < size; ++i) {
+                    WeightedString shortcut = mShortcutTargets.get(i);
+                    if (shortcut.mWord.equals(word)) {
+                        return shortcut;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Gets the bigram for the given word.
+         * Returns null if the word is not in the bigrams list.
+         */
+        public WeightedString getBigram(final String word) {
+            if (mBigrams != null) {
+                final int size = mBigrams.size();
+                for (int i = 0; i < size; ++i) {
+                    WeightedString bigram = mBigrams.get(i);
+                    if (bigram.mWord.equals(word)) {
+                        return bigram;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Updates the CharGroup with the given properties. Adds the shortcut and bigram lists to
+         * the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only
+         * updated if they are higher than the existing ones.
+         */
+        public void update(int frequency, ArrayList<WeightedString> shortcutTargets,
+                ArrayList<WeightedString> bigrams, boolean isShortcutOnly) {
+            if (frequency > mFrequency) {
+                mFrequency = frequency;
+            }
+            if (shortcutTargets != null) {
+                if (mShortcutTargets == null) {
+                    mShortcutTargets = shortcutTargets;
+                } else {
+                    final int size = shortcutTargets.size();
+                    for (int i = 0; i < size; ++i) {
+                        final WeightedString shortcut = shortcutTargets.get(i);
+                        final WeightedString existingShortcut = getShortcut(shortcut.mWord);
+                        if (existingShortcut == null) {
+                            mShortcutTargets.add(shortcut);
+                        } else if (existingShortcut.mFrequency < shortcut.mFrequency) {
+                            existingShortcut.mFrequency = shortcut.mFrequency;
+                        }
+                    }
+                }
+            }
+            if (bigrams != null) {
+                if (mBigrams == null) {
+                    mBigrams = bigrams;
+                } else {
+                    final int size = bigrams.size();
+                    for (int i = 0; i < size; ++i) {
+                        final WeightedString bigram = bigrams.get(i);
+                        final WeightedString existingBigram = getBigram(bigram.mWord);
+                        if (existingBigram == null) {
+                            mBigrams.add(bigram);
+                        } else if (existingBigram.mFrequency < bigram.mFrequency) {
+                            existingBigram.mFrequency = bigram.mFrequency;
+                        }
+                    }
+                }
+            }
+            mIsShortcutOnly = isShortcutOnly;
+        }
     }
 
     /**
@@ -128,6 +251,10 @@
      * There are no options at the moment, so this class is empty.
      */
     public static class DictionaryOptions {
+        final HashMap<String, String> mAttributes;
+        public DictionaryOptions(final HashMap<String, String> attributes) {
+            mAttributes = attributes;
+        }
     }
 
 
@@ -135,8 +262,13 @@
     public final Node mRoot;
 
     public FusionDictionary() {
-        mOptions = new DictionaryOptions();
         mRoot = new Node();
+        mOptions = new DictionaryOptions(new HashMap<String, String>());
+    }
+
+    public FusionDictionary(final HashMap<String, String> attributes) {
+        mRoot = new Node();
+        mOptions = new DictionaryOptions(attributes);
     }
 
     public FusionDictionary(final Node root, final DictionaryOptions options) {
@@ -144,19 +276,41 @@
         mOptions = options;
     }
 
+    public void addOptionAttribute(final String key, final String value) {
+        mOptions.mAttributes.put(key, value);
+    }
+
     /**
      * Helper method to convert a String to an int array.
      */
     static private int[] getCodePoints(String word) {
         final int wordLength = word.length();
         int[] array = new int[word.codePointCount(0, wordLength)];
-        for (int i = 0; i < wordLength; ++i) {
+        for (int i = 0; i < wordLength; i = word.offsetByCodePoints(i, 1)) {
             array[i] = word.codePointAt(i);
         }
         return array;
     }
 
     /**
+     * Helper method to add all words in a list as 0-frequency entries
+     *
+     * These words are added when shortcuts targets or bigrams are not found in the dictionary
+     * yet. The same words may be added later with an actual frequency - this is handled by
+     * the private version of add().
+     */
+    private void addNeutralWords(final ArrayList<WeightedString> words) {
+        if (null != words) {
+            for (WeightedString word : words) {
+                final CharGroup t = findWordInTree(mRoot, word.mWord);
+                if (null == t) {
+                    add(getCodePoints(word.mWord), 0, null, null, false /* isShortcutOnly */);
+                }
+            }
+        }
+    }
+
+    /**
      * Helper method to add a word as a string.
      *
      * This method adds a word to the dictionary with the given frequency. Optional
@@ -165,18 +319,16 @@
      *
      * @param word the word to add.
      * @param frequency the frequency of the word, in the range [0..255].
+     * @param shortcutTargets a list of shortcut targets for this word, or null.
      * @param bigrams a list of bigrams, or null.
      */
-    public void add(String word, int frequency, ArrayList<WeightedString> bigrams) {
+    public void add(final String word, final int frequency,
+            final ArrayList<WeightedString> shortcutTargets,
+            final ArrayList<WeightedString> bigrams) {
         if (null != bigrams) {
-            for (WeightedString bigram : bigrams) {
-                final CharGroup t = findWordInTree(mRoot, bigram.mWord);
-                if (null == t) {
-                    add(getCodePoints(bigram.mWord), 0, null);
-                }
-            }
+            addNeutralWords(bigrams);
         }
-        add(getCodePoints(word), frequency, bigrams);
+        add(getCodePoints(word), frequency, shortcutTargets, bigrams, false /* isShortcutOnly */);
     }
 
     /**
@@ -198,16 +350,57 @@
     }
 
     /**
+     * Helper method to add a shortcut that should not be a dictionary word.
+     *
+     * @param word the word to add.
+     * @param frequency the frequency of the word, in the range [0..255].
+     * @param shortcutTargets a list of shortcut targets. May not be null.
+     */
+    public void addShortcutOnly(final String word, final int frequency,
+            final ArrayList<WeightedString> shortcutTargets) {
+        if (null == shortcutTargets) {
+            throw new RuntimeException("Can't add a shortcut without targets");
+        }
+        add(getCodePoints(word), frequency, shortcutTargets, null, true /* isShortcutOnly */);
+    }
+
+    /**
+     * Helper method to add a new bigram to the dictionary.
+     *
+     * @param word1 the previous word of the context
+     * @param word2 the next word of the context
+     * @param frequency the bigram frequency
+     */
+    public void setBigram(final String word1, final String word2, final int frequency) {
+        CharGroup charGroup = findWordInTree(mRoot, word1);
+        if (charGroup != null) {
+            final CharGroup charGroup2 = findWordInTree(mRoot, word2);
+            if (charGroup2 == null) {
+                // TODO: refactor with the identical code in addNeutralWords
+                add(getCodePoints(word2), 0, null, null, false /* isShortcutOnly */);
+            }
+            charGroup.addBigram(word2, frequency);
+        } else {
+            throw new RuntimeException("First word of bigram not found");
+        }
+    }
+
+    /**
      * Add a word to this dictionary.
      *
-     * The bigrams, if any, have to be in the dictionary already. If they aren't,
+     * The shortcuts and bigrams, if any, have to be in the dictionary already. If they aren't,
      * an exception is thrown.
      *
      * @param word the word, as an int array.
      * @param frequency the frequency of the word, in the range [0..255].
+     * @param shortcutTargets an optional list of shortcut targets for this word (null if none).
      * @param bigrams an optional list of bigrams for this word (null if none).
+     * @param isShortcutOnly whether this should be a shortcut only.
      */
-    private void add(int[] word, int frequency, ArrayList<WeightedString> bigrams) {
+    private void add(final int[] word, final int frequency,
+            final ArrayList<WeightedString> shortcutTargets,
+            final ArrayList<WeightedString> bigrams,
+            final boolean isShortcutOnly) {
         assert(frequency >= 0 && frequency <= 255);
         Node currentNode = mRoot;
         int charIndex = 0;
@@ -231,7 +424,8 @@
             // No node at this point to accept the word. Create one.
             final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
             final CharGroup newGroup = new CharGroup(
-                    Arrays.copyOfRange(word, charIndex, word.length), bigrams, frequency);
+                    Arrays.copyOfRange(word, charIndex, word.length),
+                    shortcutTargets, bigrams, frequency, isShortcutOnly);
             currentNode.mData.add(insertionIndex, newGroup);
             checkStack(currentNode);
         } else {
@@ -239,60 +433,50 @@
             if (differentCharIndex == currentGroup.mChars.length) {
                 if (charIndex + differentCharIndex >= word.length) {
                     // The new word is a prefix of an existing word, but the node on which it
-                    // should end already exists as is.
-                    if (currentGroup.mFrequency > 0) {
-                        throw new RuntimeException("Such a word already exists in the dictionary : "
-                                + new String(word, 0, word.length));
-                    } else {
-                        final CharGroup newNode = new CharGroup(currentGroup.mChars,
-                                bigrams, frequency, currentGroup.mChildren);
-                        currentNode.mData.set(nodeIndex, newNode);
-                        checkStack(currentNode);
-                    }
+                    // should end already exists as is. Since the old CharNode was not a terminal, 
+                    // make it one by filling in its frequency and other attributes
+                    currentGroup.update(frequency, shortcutTargets, bigrams, isShortcutOnly);
                 } else {
                     // The new word matches the full old word and extends past it.
                     // We only have to create a new node and add it to the end of this.
                     final CharGroup newNode = new CharGroup(
                             Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
-                                    bigrams, frequency);
+                                    shortcutTargets, bigrams, frequency, isShortcutOnly);
                     currentGroup.mChildren = new Node();
                     currentGroup.mChildren.mData.add(newNode);
                 }
             } else {
                 if (0 == differentCharIndex) {
-                    // Exact same word. Check the frequency is 0 or -1, and update.
-                    if (0 != frequency) {
-                        if (0 < currentGroup.mFrequency) {
-                            throw new RuntimeException("This word already exists with frequency "
-                                    + currentGroup.mFrequency + " : "
-                                    + new String(word, 0, word.length));
-                        }
-                        final CharGroup newGroup = new CharGroup(word,
-                                currentGroup.mBigrams, frequency, currentGroup.mChildren);
-                        currentNode.mData.set(nodeIndex, newGroup);
-                    }
+                    // Exact same word. Update the frequency if higher. This will also add the
+                    // new bigrams to the existing bigram list if it already exists.
+                    currentGroup.update(frequency, shortcutTargets, bigrams, isShortcutOnly);
                 } else {
                     // Partial prefix match only. We have to replace the current node with a node
                     // containing the current prefix and create two new ones for the tails.
                     Node newChildren = new Node();
                     final CharGroup newOldWord = new CharGroup(
                             Arrays.copyOfRange(currentGroup.mChars, differentCharIndex,
-                                    currentGroup.mChars.length),
-                            currentGroup.mBigrams, currentGroup.mFrequency, currentGroup.mChildren);
+                                    currentGroup.mChars.length), currentGroup.mShortcutTargets,
+                            currentGroup.mBigrams, currentGroup.mFrequency, currentGroup.mChildren,
+                            currentGroup.mIsShortcutOnly);
                     newChildren.mData.add(newOldWord);
 
                     final CharGroup newParent;
                     if (charIndex + differentCharIndex >= word.length) {
                         newParent = new CharGroup(
                                 Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
-                                        bigrams, frequency, newChildren);
+                                shortcutTargets, bigrams, frequency, newChildren, isShortcutOnly);
                     } else {
+                        // isShortcutOnly makes no sense for non-terminal nodes. The following node
+                        // is non-terminal (frequency 0 in FusionDictionary representation) so we
+                        // pass false for isShortcutOnly
                         newParent = new CharGroup(
                                 Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
-                                        null, -1, newChildren);
+                                null, null, -1, newChildren, false /* isShortcutOnly */);
                         final CharGroup newWord = new CharGroup(
                                 Arrays.copyOfRange(word, charIndex + differentCharIndex,
-                                        word.length), bigrams, frequency);
+                                        word.length), shortcutTargets, bigrams, frequency,
+                                        isShortcutOnly);
                         final int addIndex = word[charIndex + differentCharIndex]
                                 > currentGroup.mChars[differentCharIndex] ? 1 : 0;
                         newChildren.mData.add(addIndex, newWord);
@@ -337,16 +521,11 @@
      * is ignored.
      * This comparator imposes orderings that are inconsistent with equals.
      */
-    static private class CharGroupComparator implements java.util.Comparator {
-        public int compare(Object o1, Object o2) {
-            final CharGroup c1 = (CharGroup)o1;
-            final CharGroup c2 = (CharGroup)o2;
+    static private class CharGroupComparator implements java.util.Comparator<CharGroup> {
+        public int compare(CharGroup c1, CharGroup c2) {
             if (c1.mChars[0] == c2.mChars[0]) return 0;
             return c1.mChars[0] < c2.mChars[0] ? -1 : 1;
         }
-        public boolean equals(Object o) {
-            return o instanceof CharGroupComparator;
-        }
     }
     final static private CharGroupComparator CHARGROUP_COMPARATOR = new CharGroupComparator();
 
@@ -354,8 +533,9 @@
      * Finds the insertion index of a character within a node.
      */
     private static int findInsertionIndex(final Node node, int character) {
-        final List data = node.mData;
-        final CharGroup reference = new CharGroup(new int[] { character }, null, 0);
+        final ArrayList<CharGroup> data = node.mData;
+        final CharGroup reference = new CharGroup(new int[] { character }, null, null, 0,
+                false /* isShortcutOnly */);
         int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
         return result >= 0 ? result : -result - 1;
     }
@@ -399,6 +579,16 @@
     }
 
     /**
+     * Helper method to find out whether a word is in the dict or not.
+     */
+    public boolean hasWord(final String s) {
+        if (null == s || "".equals(s)) {
+            throw new RuntimeException("Can't search for a null or empty string");
+        }
+        return null != findWordInTree(mRoot, s);
+    }
+
+    /**
      * Recursively count the number of character groups in a given branch of the trie.
      *
      * @param node the parent node.
@@ -573,7 +763,8 @@
                     }
                     if (currentGroup.mFrequency >= 0)
                         return new Word(mCurrentString.toString(), currentGroup.mFrequency,
-                                currentGroup.mBigrams);
+                                currentGroup.mShortcutTargets, currentGroup.mBigrams,
+                                currentGroup.mIsShortcutOnly);
                 } else {
                     mPositions.removeLast();
                     currentPos = mPositions.getLast();
diff --git a/tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
similarity index 95%
rename from tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java
rename to java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
index badb2ff..cff8d6f 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java
+++ b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
 /**
  * Wrapper to redirect log events to the right output medium.
diff --git a/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
similarity index 90%
rename from tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
rename to java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
index e502021..5b41d27 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
+++ b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
@@ -14,12 +14,12 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
 /**
  * A not-yet-resolved attribute.
  *
- * An attribute is either a bigram or an shortcut.
+ * An attribute is either a bigram or a shortcut.
  * All instances of this class are always immutable.
  */
 public class PendingAttribute {
diff --git a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
similarity index 94%
rename from tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
rename to java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
index 511ad56..bd42fb8 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
 /**
  * Simple exception thrown when a file format is not recognized.
diff --git a/tools/makedict/src/com/android/inputmethod/latin/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
similarity index 60%
rename from tools/makedict/src/com/android/inputmethod/latin/Word.java
rename to java/src/com/android/inputmethod/latin/makedict/Word.java
index 916165a..4e0ab10 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -14,11 +14,12 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
-import com.android.inputmethod.latin.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Utility class for a word with a frequency.
@@ -28,12 +29,30 @@
 public class Word implements Comparable<Word> {
     final String mWord;
     final int mFrequency;
+    final boolean mIsShortcutOnly;
+    final ArrayList<WeightedString> mShortcutTargets;
     final ArrayList<WeightedString> mBigrams;
 
-    public Word(String word, int frequency, ArrayList<WeightedString> bigrams) {
+    private int mHashCode = 0;
+
+    public Word(final String word, final int frequency,
+            final ArrayList<WeightedString> shortcutTargets,
+            final ArrayList<WeightedString> bigrams, final boolean isShortcutOnly) {
         mWord = word;
         mFrequency = frequency;
+        mShortcutTargets = shortcutTargets;
         mBigrams = bigrams;
+        mIsShortcutOnly = isShortcutOnly;
+    }
+
+    private static int computeHashCode(Word word) {
+        return Arrays.hashCode(new Object[] {
+                word.mWord,
+                word.mFrequency,
+                word.mIsShortcutOnly,
+                word.mShortcutTargets.hashCode(),
+                word.mBigrams.hashCode()
+        });
     }
 
     /**
@@ -57,9 +76,20 @@
      */
     @Override
     public boolean equals(Object o) {
+        if (o == this) return true;
         if (!(o instanceof Word)) return false;
         Word w = (Word)o;
         return mFrequency == w.mFrequency && mWord.equals(w.mWord)
+                && mIsShortcutOnly == w.mIsShortcutOnly
+                && mShortcutTargets.equals(w.mShortcutTargets)
                 && mBigrams.equals(w.mBigrams);
     }
+
+    @Override
+    public int hashCode() {
+        if (mHashCode == 0) {
+            mHashCode = computeHashCode(this);
+        }
+        return mHashCode;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 095c2c5..7b13e40 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -17,33 +17,37 @@
 package com.android.inputmethod.latin.spellcheck;
 
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.Resources;
+import android.preference.PreferenceManager;
 import android.service.textservice.SpellCheckerService;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
 
-import com.android.inputmethod.compat.ArraysCompatUtils;
+import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Dictionary.DataType;
 import com.android.inputmethod.latin.Dictionary.WordCallback;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
 import com.android.inputmethod.latin.Flag;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.WhitelistDictionary;
 import com.android.inputmethod.latin.WordComposer;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeMap;
@@ -51,11 +55,14 @@
 /**
  * Service for spell checking, using LatinIME's dictionaries and mechanisms.
  */
-public class AndroidSpellCheckerService extends SpellCheckerService {
+public class AndroidSpellCheckerService extends SpellCheckerService
+        implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
     private static final boolean DBG = false;
     private static final int POOL_SIZE = 2;
 
+    public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
+
     private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
     private static final int CAPITALIZE_FIRST = 1; // First only
     private static final int CAPITALIZE_ALL = 2; // All caps
@@ -82,15 +89,100 @@
 
     // The threshold for a candidate to be offered as a suggestion.
     private double mSuggestionThreshold;
-    // The threshold for a suggestion to be considered "likely".
-    private double mLikelyThreshold;
+    // The threshold for a suggestion to be considered "recommended".
+    private double mRecommendedThreshold;
+    // Whether to use the contacts dictionary
+    private boolean mUseContactsDictionary;
+    private final Object mUseContactsLock = new Object();
+
+    private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
+            new HashSet<WeakReference<DictionaryCollection>>();
+
+    public static final int SCRIPT_LATIN = 0;
+    public static final int SCRIPT_CYRILLIC = 1;
+    private static final TreeMap<String, Integer> mLanguageToScript;
+    static {
+        // List of the supported languages and their associated script. We won't check
+        // words written in another script than the selected script, because we know we
+        // don't have those in our dictionary so we will underline everything and we
+        // will never have any suggestions, so it makes no sense checking them.
+        mLanguageToScript = new TreeMap<String, Integer>();
+        mLanguageToScript.put("en", SCRIPT_LATIN);
+        mLanguageToScript.put("fr", SCRIPT_LATIN);
+        mLanguageToScript.put("de", SCRIPT_LATIN);
+        mLanguageToScript.put("nl", SCRIPT_LATIN);
+        mLanguageToScript.put("cs", SCRIPT_LATIN);
+        mLanguageToScript.put("es", SCRIPT_LATIN);
+        mLanguageToScript.put("it", SCRIPT_LATIN);
+        mLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+    }
 
     @Override public void onCreate() {
         super.onCreate();
         mSuggestionThreshold =
                 Double.parseDouble(getString(R.string.spellchecker_suggestion_threshold_value));
-        mLikelyThreshold =
-                Double.parseDouble(getString(R.string.spellchecker_likely_threshold_value));
+        mRecommendedThreshold =
+                Double.parseDouble(getString(R.string.spellchecker_recommended_threshold_value));
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        prefs.registerOnSharedPreferenceChangeListener(this);
+        onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
+    }
+
+    private static int getScriptFromLocale(final Locale locale) {
+        final Integer script = mLanguageToScript.get(locale.getLanguage());
+        if (null == script) {
+            throw new RuntimeException("We have been called with an unsupported language: \""
+                    + locale.getLanguage() + "\". Framework bug?");
+        }
+        return script;
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+        if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
+        synchronized(mUseContactsLock) {
+            mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
+            if (mUseContactsDictionary) {
+                startUsingContactsDictionaryLocked();
+            } else {
+                stopUsingContactsDictionaryLocked();
+            }
+        }
+    }
+
+    private void startUsingContactsDictionaryLocked() {
+        if (null == mContactsDictionary) {
+            mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+        }
+        final Iterator<WeakReference<DictionaryCollection>> iterator =
+                mDictionaryCollectionsList.iterator();
+        while (iterator.hasNext()) {
+            final WeakReference<DictionaryCollection> dictRef = iterator.next();
+            final DictionaryCollection dict = dictRef.get();
+            if (null == dict) {
+                iterator.remove();
+            } else {
+                dict.addDictionary(mContactsDictionary);
+            }
+        }
+    }
+
+    private void stopUsingContactsDictionaryLocked() {
+        if (null == mContactsDictionary) return;
+        final SynchronouslyLoadedContactsDictionary contactsDict = mContactsDictionary;
+        mContactsDictionary = null;
+        final Iterator<WeakReference<DictionaryCollection>> iterator =
+                mDictionaryCollectionsList.iterator();
+        while (iterator.hasNext()) {
+            final WeakReference<DictionaryCollection> dictRef = iterator.next();
+            final DictionaryCollection dict = dictRef.get();
+            if (null == dict) {
+                iterator.remove();
+            } else {
+                dict.removeDictionary(contactsDict);
+            }
+        }
+        contactsDict.close();
     }
 
     @Override
@@ -110,10 +202,11 @@
     private static class SuggestionsGatherer implements WordCallback {
         public static class Result {
             public final String[] mSuggestions;
-            public final boolean mHasLikelySuggestions;
-            public Result(final String[] gatheredSuggestions, final boolean hasLikelySuggestions) {
+            public final boolean mHasRecommendedSuggestions;
+            public Result(final String[] gatheredSuggestions,
+                    final boolean hasRecommendedSuggestions) {
                 mSuggestions = gatheredSuggestions;
-                mHasLikelySuggestions = hasLikelySuggestions;
+                mHasRecommendedSuggestions = hasRecommendedSuggestions;
             }
         }
 
@@ -121,7 +214,7 @@
         private final int[] mScores;
         private final String mOriginalText;
         private final double mSuggestionThreshold;
-        private final double mLikelyThreshold;
+        private final double mRecommendedThreshold;
         private final int mMaxLength;
         private int mLength = 0;
 
@@ -131,10 +224,10 @@
         private int mBestScore = Integer.MIN_VALUE; // As small as possible
 
         SuggestionsGatherer(final String originalText, final double suggestionThreshold,
-                final double likelyThreshold, final int maxLength) {
+                final double recommendedThreshold, final int maxLength) {
             mOriginalText = originalText;
             mSuggestionThreshold = suggestionThreshold;
-            mLikelyThreshold = likelyThreshold;
+            mRecommendedThreshold = recommendedThreshold;
             mMaxLength = maxLength;
             mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
             mScores = new int[mMaxLength];
@@ -142,8 +235,8 @@
 
         @Override
         synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
-                int dicTypeId, DataType dataType) {
-            final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
+                int dicTypeId, int dataType) {
+            final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score);
             // binarySearch returns the index if the element exists, and -<insertion index> - 1
             // if it doesn't. See documentation for binarySearch.
             final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
@@ -175,7 +268,7 @@
             // make the threshold.
             final String wordString = new String(word, wordOffset, wordLength);
             final double normalizedScore =
-                    Utils.calcNormalizedScore(mOriginalText, wordString, score);
+                    BinaryDictionary.calcNormalizedScore(mOriginalText, wordString, score);
             if (normalizedScore < mSuggestionThreshold) {
                 if (DBG) Log.i(TAG, wordString + " does not make the score threshold");
                 return true;
@@ -198,19 +291,19 @@
 
         public Result getResults(final int capitalizeType, final Locale locale) {
             final String[] gatheredSuggestions;
-            final boolean hasLikelySuggestions;
+            final boolean hasRecommendedSuggestions;
             if (0 == mLength) {
                 // Either we found no suggestions, or we found some BUT the max length was 0.
                 // If we found some mBestSuggestion will not be null. If it is null, then
                 // we found none, regardless of the max length.
                 if (null == mBestSuggestion) {
                     gatheredSuggestions = null;
-                    hasLikelySuggestions = false;
+                    hasRecommendedSuggestions = false;
                 } else {
                     gatheredSuggestions = EMPTY_STRING_ARRAY;
-                    final double normalizedScore =
-                            Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
-                    hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+                    final double normalizedScore = BinaryDictionary.calcNormalizedScore(
+                            mOriginalText, mBestSuggestion, mBestScore);
+                    hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
                 }
             } else {
                 if (DBG) {
@@ -222,7 +315,7 @@
                     }
                 }
                 Collections.reverse(mSuggestions);
-                Utils.removeDupes(mSuggestions);
+                StringUtils.removeDupes(mSuggestions);
                 if (CAPITALIZE_ALL == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // get(i) returns a CharSequence which is actually a String so .toString()
@@ -232,7 +325,7 @@
                 } else if (CAPITALIZE_FIRST == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // Likewise
-                        mSuggestions.set(i, Utils.toTitleCase(mSuggestions.get(i).toString(),
+                        mSuggestions.set(i, StringUtils.toTitleCase(mSuggestions.get(i).toString(),
                                 locale));
                     }
                 }
@@ -243,21 +336,27 @@
                 final int bestScore = mScores[mLength - 1];
                 final CharSequence bestSuggestion = mSuggestions.get(0);
                 final double normalizedScore =
-                        Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
-                hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+                        BinaryDictionary.calcNormalizedScore(
+                                mOriginalText, bestSuggestion.toString(), bestScore);
+                hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
                 if (DBG) {
                     Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
                     Log.i(TAG, "Normalized score = " + normalizedScore
-                            + " (threshold " + mLikelyThreshold
-                            + ") => hasLikelySuggestions = " + hasLikelySuggestions);
+                            + " (threshold " + mRecommendedThreshold
+                            + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
                 }
             }
-            return new Result(gatheredSuggestions, hasLikelySuggestions);
+            return new Result(gatheredSuggestions, hasRecommendedSuggestions);
         }
     }
 
     @Override
     public boolean onUnbind(final Intent intent) {
+        closeAllDictionaries();
+        return false;
+    }
+
+    private void closeAllDictionaries() {
         final Map<String, DictionaryPool> oldPools = mDictionaryPools;
         mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
         final Map<String, Dictionary> oldUserDictionaries = mUserDictionaries;
@@ -273,15 +372,16 @@
         for (Dictionary dict : oldWhitelistDictionaries.values()) {
             dict.close();
         }
-        if (null != mContactsDictionary) {
-            // The synchronously loaded contacts dictionary should have been in one
-            // or several pools, but it is shielded against multiple closing and it's
-            // safe to call it several times.
-            final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
-            mContactsDictionary = null;
-            dictToClose.close();
+        synchronized(mUseContactsLock) {
+            if (null != mContactsDictionary) {
+                // The synchronously loaded contacts dictionary should have been in one
+                // or several pools, but it is shielded against multiple closing and it's
+                // safe to call it several times.
+                final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
+                mContactsDictionary = null;
+                dictToClose.close();
+            }
         }
-        return false;
     }
 
     private DictionaryPool getDictionaryPool(final String locale) {
@@ -295,9 +395,11 @@
     }
 
     public DictAndProximity createDictAndProximity(final Locale locale) {
-        final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo();
+        final int script = getScriptFromLocale(locale);
+        final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
+                SpellCheckerProximityInfo.getProximityForScript(script));
         final Resources resources = getResources();
-        final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+        final int fallbackResourceId = DictionaryFactory.getMainDictionaryResourceId(resources);
         final DictionaryCollection dictionaryCollection =
                 DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId,
                         USE_FULL_EDIT_DISTANCE_FLAG_ARRAY);
@@ -314,11 +416,16 @@
             mWhitelistDictionaries.put(localeStr, whitelistDictionary);
         }
         dictionaryCollection.addDictionary(whitelistDictionary);
-        if (null == mContactsDictionary) {
-            mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+        synchronized(mUseContactsLock) {
+            if (mUseContactsDictionary) {
+                if (null == mContactsDictionary) {
+                    mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+                }
+            }
+            dictionaryCollection.addDictionary(mContactsDictionary);
+            mDictionaryCollectionsList.add(
+                    new WeakReference<DictionaryCollection>(dictionaryCollection));
         }
-        // TODO: add a setting to use or not contacts when checking spelling
-        dictionaryCollection.addDictionary(mContactsDictionary);
         return new DictAndProximity(dictionaryCollection, proximityInfo);
     }
 
@@ -327,9 +434,9 @@
         // If the first char is not uppercase, then the word is either all lower case,
         // and in either case we return CAPITALIZE_NONE.
         if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
-        final int len = text.codePointCount(0, text.length());
+        final int len = text.length();
         int capsCount = 1;
-        for (int i = 1; i < len; ++i) {
+        for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) {
             if (1 != capsCount && i != capsCount) break;
             if (Character.isUpperCase(text.codePointAt(i))) ++capsCount;
         }
@@ -346,6 +453,8 @@
         private DictionaryPool mDictionaryPool;
         // Likewise
         private Locale mLocale;
+        // Cache this for performance
+        private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
 
         private final AndroidSpellCheckerService mService;
 
@@ -358,17 +467,51 @@
             final String localeString = getLocale();
             mDictionaryPool = mService.getDictionaryPool(localeString);
             mLocale = LocaleUtils.constructLocaleFromString(localeString);
+            mScript = getScriptFromLocale(mLocale);
+        }
+
+        /*
+         * Returns whether the code point is a letter that makes sense for the specified
+         * locale for this spell checker.
+         * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
+         * and is limited to EFIGS languages and Russian.
+         * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
+         * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
+         */
+        private static boolean isLetterCheckableByLanguage(final int codePoint,
+                final int script) {
+            switch (script) {
+            case SCRIPT_LATIN:
+                // Our supported latin script dictionaries (EFIGS) at the moment only include
+                // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
+                // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
+                // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
+                // excluded from isLetter anyway.
+                return codePoint <= 0x2AF && Character.isLetter(codePoint);
+            case SCRIPT_CYRILLIC:
+                // All Cyrillic characters are in the 400~52F block. There are some in the upper
+                // Unicode range, but they are archaic characters that are not used in modern
+                // russian and are not used by our dictionary.
+                return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+            default:
+                // Should never come here
+                throw new RuntimeException("Impossible value of script: " + script);
+            }
         }
 
         /**
          * Finds out whether a particular string should be filtered out of spell checking.
          *
-         * This will loosely match URLs, numbers, symbols.
+         * This will loosely match URLs, numbers, symbols. To avoid always underlining words that
+         * we know we will never recognize, this accepts a script identifier that should be one
+         * of the SCRIPT_* constants defined above, to rule out quickly characters from very
+         * different languages.
          *
          * @param text the string to evaluate.
+         * @param script the identifier for the script this spell checker recognizes
          * @return true if we should filter this text out, false otherwise
          */
-        private boolean shouldFilterOut(final String text) {
+        private static boolean shouldFilterOut(final String text, final int script) {
             if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
 
             // TODO: check if an equivalent processing can't be done more quickly with a
@@ -376,20 +519,19 @@
             // Filter by first letter
             final int firstCodePoint = text.codePointAt(0);
             // Filter out words that don't start with a letter or an apostrophe
-            if (!Character.isLetter(firstCodePoint)
+            if (!isLetterCheckableByLanguage(firstCodePoint, script)
                     && '\'' != firstCodePoint) return true;
 
             // Filter contents
             final int length = text.length();
             int letterCount = 0;
-            for (int i = 0; i < length; ++i) {
+            for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
                 final int codePoint = text.codePointAt(i);
                 // Any word containing a '@' is probably an e-mail address
                 // Any word containing a '/' is probably either an ad-hoc combination of two
                 // words or a URI - in either case we don't want to spell check that
-                if ('@' == codePoint
-                        || '/' == codePoint) return true;
-                if (Character.isLetter(codePoint)) ++letterCount;
+                if ('@' == codePoint || '/' == codePoint) return true;
+                if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
             }
             // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
             // in this word are letters
@@ -408,7 +550,7 @@
             try {
                 final String text = textInfo.getText();
 
-                if (shouldFilterOut(text)) {
+                if (shouldFilterOut(text, mScript)) {
                     DictAndProximity dictInfo = null;
                     try {
                         dictInfo = mDictionaryPool.takeOrGetNull();
@@ -426,22 +568,21 @@
 
                 // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
                 final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
-                        mService.mSuggestionThreshold, mService.mLikelyThreshold, suggestionsLimit);
+                        mService.mSuggestionThreshold, mService.mRecommendedThreshold,
+                        suggestionsLimit);
                 final WordComposer composer = new WordComposer();
                 final int length = text.length();
-                for (int i = 0; i < length; ++i) {
-                    final int character = text.codePointAt(i);
-                    final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
-                    final int[] proximities;
-                    if (-1 == proximityIndex) {
-                        proximities = new int[] { character };
+                for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
+                    final int codePoint = text.codePointAt(i);
+                    // The getXYForCodePointAndScript method returns (Y << 16) + X
+                    final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
+                            codePoint, mScript);
+                    if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
+                        composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
+                                WordComposer.NOT_A_COORDINATE, null);
                     } else {
-                        proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
-                                proximityIndex,
-                                proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
+                        composer.add(codePoint, xy & 0xFFFF, xy >> 16, null);
                     }
-                    composer.add(character, proximities,
-                            WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
                 }
 
                 final int capitalizeType = getCapitalizationType(text);
@@ -475,7 +616,7 @@
                             + suggestionsLimit);
                     Log.i(TAG, "IsInDict = " + isInDict);
                     Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
-                    Log.i(TAG, "HasLikelySuggestions = " + result.mHasLikelySuggestions);
+                    Log.i(TAG, "HasRecommendedSuggestions = " + result.mHasRecommendedSuggestions);
                     if (null != result.mSuggestions) {
                         for (String suggestion : result.mSuggestions) {
                             Log.i(TAG, suggestion);
@@ -483,10 +624,13 @@
                     }
                 }
 
-                // TODO: actually use result.mHasLikelySuggestions
                 final int flags =
                         (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
-                                : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO);
+                                : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+                        | (result.mHasRecommendedSuggestions
+                                ? SuggestionsInfoCompatUtils
+                                        .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
+                                : 0);
                 return new SuggestionsInfo(flags, result.mSuggestions);
             } catch (RuntimeException e) {
                 // Don't kill the keyboard if there is a bug in the spell checker
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index d5b04b2..0103e84 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -22,72 +22,193 @@
 import java.util.TreeMap;
 
 public class SpellCheckerProximityInfo {
-    final private static int NUL = KeyDetector.NOT_A_CODE;
+    /* public for test */
+    final public static int NUL = KeyDetector.NOT_A_CODE;
 
     // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
     // native code - this value is passed at creation of the binary object and reused
     // as the size of the passed array afterwards so they can't be different.
     final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
 
-    // This is a map from the code point to the index in the PROXIMITY array.
-    // At the time the native code to read the binary dictionary needs the proximity info be passed
-    // as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input character.
-    // Since we need to build such an array, we want to be able to search in our big proximity data
-    // quickly by character, and a map is probably the best way to do this.
-    final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+    // The number of keys in a row of the grid used by the spell checker.
+    final public static int PROXIMITY_GRID_WIDTH = 11;
+    // The number of rows in the grid used by the spell checker.
+    final public static int PROXIMITY_GRID_HEIGHT = 3;
 
-    // The proximity here is the union of
-    // - the proximity for a QWERTY keyboard.
-    // - the proximity for an AZERTY keyboard.
-    // - the proximity for a QWERTZ keyboard.
-    // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
-    //
-    // The reasoning behind this construction is, almost any alphabetic text we may want
-    // to spell check has been entered with one of the keyboards above. Also, specifically
-    // to English, many spelling errors consist of the last vowel of the word being wrong
-    // because in English vowels tend to merge with each other in pronunciation.
-    final public static int[] PROXIMITY = {
-        'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
-        'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
-        'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+    final private static int NOT_AN_INDEX = -1;
+    final public static int NOT_A_COORDINATE_PAIR = -1;
 
-        'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
-        's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL,
-        'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-        'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
-        'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-    };
-    static {
-        for (int i = 0; i < PROXIMITY.length; i += ROW_SIZE) {
-            if (NUL != PROXIMITY[i]) INDICES.put(PROXIMITY[i], i);
+    // Helper methods
+    final protected static void buildProximityIndices(final int[] proximity,
+            final TreeMap<Integer, Integer> indices) {
+        for (int i = 0; i < proximity.length; i += ROW_SIZE) {
+            if (NUL != proximity[i]) indices.put(proximity[i], i / ROW_SIZE);
         }
     }
-    public static int getIndexOf(int characterCode) {
-        final Integer result = INDICES.get(characterCode);
-        if (null == result) return -1;
+    final protected static int computeIndex(final int characterCode,
+            final TreeMap<Integer, Integer> indices) {
+        final Integer result = indices.get(characterCode);
+        if (null == result) return NOT_AN_INDEX;
         return result;
     }
+
+    private static class Latin {
+        // This is a map from the code point to the index in the PROXIMITY array.
+        // At the time the native code to read the binary dictionary needs the proximity info be
+        // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
+        // character.
+        // Since we need to build such an array, we want to be able to search in our big proximity
+        // data quickly by character, and a map is probably the best way to do this.
+        final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+
+        // The proximity here is the union of
+        // - the proximity for a QWERTY keyboard.
+        // - the proximity for an AZERTY keyboard.
+        // - the proximity for a QWERTZ keyboard.
+        // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
+        //
+        // The reasoning behind this construction is, almost any alphabetic text we may want
+        // to spell check has been entered with one of the keyboards above. Also, specifically
+        // to English, many spelling errors consist of the last vowel of the word being wrong
+        // because in English vowels tend to merge with each other in pronunciation.
+        final static int[] PROXIMITY = {
+            // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
+            // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
+            // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+            'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+            'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
+            'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+            // Proximity for row 2. See comment above about size.
+            'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+            's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+            // Proximity for row 3. See comment above about size.
+            'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
+            'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+        };
+        static {
+            buildProximityIndices(PROXIMITY, INDICES);
+        }
+        static int getIndexOf(int characterCode) {
+            return computeIndex(characterCode, INDICES);
+        }
+    }
+
+    private static class Cyrillic {
+        final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+        // TODO: The following table is solely based on the keyboard layout. Consult with Russian
+        // speakers on commonly misspelled words/letters.
+        final static int[] PROXIMITY = {
+            // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
+            // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
+            // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+            'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'к', 'у', 'в', 'а', 'п', 'е', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'е', 'к', 'а', 'п', 'р', 'н', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'н', 'е', 'п', 'р', 'о', 'г', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'г', 'н', 'р', 'о', 'л', 'ш', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ш', 'г', 'о', 'л', 'д', 'щ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'щ', 'ш', 'л', 'д', 'ж', 'з', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+            // Proximity for row 2. See comment above about size.
+            'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'а', 'у', 'к', 'е', 'в', 'п', 'ч', 'с', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'п', 'к', 'е', 'н', 'а', 'р', 'с', 'м', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'р', 'е', 'н', 'г', 'п', 'о', 'м', 'и', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'о', 'н', 'г', 'ш', 'р', 'л', 'и', 'т', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'л', 'г', 'ш', 'щ', 'о', 'д', 'т', 'ь', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'д', 'ш', 'щ', 'з', 'л', 'ж', 'ь', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+            // Proximity for row 3. See comment above about size.
+            'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'м', 'а', 'п', 'р', 'с', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'и', 'п', 'р', 'о', 'м', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'т', 'р', 'о', 'л', 'и', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+        };
+        static {
+            buildProximityIndices(PROXIMITY, INDICES);
+        }
+        static int getIndexOf(int characterCode) {
+            return computeIndex(characterCode, INDICES);
+        }
+    }
+
+    public static int[] getProximityForScript(final int script) {
+        switch (script) {
+            case AndroidSpellCheckerService.SCRIPT_LATIN:
+                return Latin.PROXIMITY;
+            case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+                return Cyrillic.PROXIMITY;
+            default:
+                throw new RuntimeException("Wrong script supplied: " + script);
+        }
+    }
+
+    private static int getIndexOfCodeForScript(final int codePoint, final int script) {
+        switch (script) {
+            case AndroidSpellCheckerService.SCRIPT_LATIN:
+                return Latin.getIndexOf(codePoint);
+            case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+                return Cyrillic.getIndexOf(codePoint);
+            default:
+                throw new RuntimeException("Wrong script supplied: " + script);
+        }
+    }
+
+    // Returns (Y << 16) + X to avoid creating a temporary object. This is okay because
+    // X and Y are limited to PROXIMITY_GRID_WIDTH resp. PROXIMITY_GRID_HEIGHT which is very
+    // inferior to 1 << 16
+    // As an exception, this returns NOT_A_COORDINATE_PAIR if the key is not on the grid
+    public static int getXYForCodePointAndScript(final int codePoint, final int script) {
+        final int index = getIndexOfCodeForScript(codePoint, script);
+        if (NOT_AN_INDEX == index) return NOT_A_COORDINATE_PAIR;
+        final int y = index / PROXIMITY_GRID_WIDTH;
+        final int x = index % PROXIMITY_GRID_WIDTH;
+        if (y > PROXIMITY_GRID_HEIGHT) {
+            // Safety check, should be entirely useless
+            throw new RuntimeException("Wrong y coordinate in spell checker proximity");
+        }
+        return (y << 16) + x;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
similarity index 82%
rename from java/src/com/android/inputmethod/latin/MoreSuggestions.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 9a59ef2..dd83a0c 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -14,37 +14,34 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
-import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Utils;
 
 public class MoreSuggestions extends Keyboard {
-    private static final boolean DBG = LatinImeLogger.sDBG;
-
     public static final int SUGGESTION_CODE_BASE = 1024;
 
-    private MoreSuggestions(Builder.MoreSuggestionsParam params) {
+    MoreSuggestions(Builder.MoreSuggestionsParam params) {
         super(params);
     }
 
-    public static class Builder extends KeyboardBuilder<Builder.MoreSuggestionsParam> {
+    public static class Builder extends Keyboard.Builder<Builder.MoreSuggestionsParam> {
         private final MoreSuggestionsView mPaneView;
         private SuggestedWords mSuggestions;
         private int mFromPos;
         private int mToPos;
 
-        public static class MoreSuggestionsParam extends KeyboardParams {
+        public static class MoreSuggestionsParam extends Keyboard.Params {
             private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS];
             private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS];
             private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS];
@@ -55,25 +52,24 @@
             public int mDividerWidth;
 
             public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth,
-                    int maxRow, KeyboardView view) {
+                    int maxRow, MoreSuggestionsView view) {
                 clearKeys();
-                final Paint paint = new Paint();
-                paint.setAntiAlias(true);
                 final Resources res = view.getContext().getResources();
                 mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
-                // TODO: Drawable itself should has an alpha value.
+                // TODO: Drawable itself should have an alpha value.
                 mDivider.setAlpha(128);
                 mDividerWidth = mDivider.getIntrinsicWidth();
                 final int padding = (int) res.getDimension(
                         R.dimen.more_suggestions_key_horizontal_padding);
+                final Paint paint = view.newDefaultLabelPaint();
 
                 int row = 0;
                 int pos = fromPos, rowStartPos = fromPos;
                 final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS);
                 while (pos < size) {
-                    final CharSequence word = suggestions.getWord(pos);
+                    final String word = suggestions.getWord(pos).toString();
                     // TODO: Should take care of text x-scaling.
-                    mWidths[pos] = (int)view.getDefaultLabelWidth(word, paint) + padding;
+                    mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
                     final int numColumn = pos - rowStartPos + 1;
                     final int columnWidth =
                             (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
@@ -176,9 +172,9 @@
 
         public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth,
                 int minWidth, int maxRow) {
-            final Keyboard keyboard = KeyboardSwitcher.getInstance().getLatinKeyboard();
+            final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
             final int xmlId = R.xml.kbd_suggestions_pane_template;
-            load(keyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
+            load(xmlId, keyboard.mId);
             mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2;
 
             final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
@@ -189,13 +185,19 @@
             return this;
         }
 
-        private static String getDebugInfo(SuggestedWords suggestions, int pos) {
-            if (!DBG) return null;
-            final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-            if (wordInfo == null) return null;
-            final String info = wordInfo.getDebugString();
-            if (TextUtils.isEmpty(info)) return null;
-            return info;
+        private static class Divider extends Key.Spacer {
+            private final Drawable mIcon;
+
+            public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width,
+                    int height) {
+                super(params, x, y, width, height);
+                mIcon = icon;
+            }
+
+            @Override
+            public Drawable getIcon(KeyboardIconsSet iconSet) {
+                return mIcon;
+            }
         }
 
         @Override
@@ -206,19 +208,19 @@
                 final int y = params.getY(pos);
                 final int width = params.getWidth(pos);
                 final String word = mSuggestions.getWord(pos).toString();
-                final String info = getDebugInfo(mSuggestions, pos);
+                final String info = Utils.getDebugInfo(mSuggestions, pos);
                 final int index = pos + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
-                        params, word, info, null, index, null, x, y, width,
-                        params.mDefaultRowHeight);
+                        params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y,
+                        width, params.mDefaultRowHeight, 0);
                 params.markAsEdgeKey(key, pos);
                 params.onAddKey(key);
                 final int columnNumber = params.getColumnNumber(pos);
                 final int numColumnInRow = params.getNumColumnInRow(pos);
                 if (columnNumber < numColumnInRow - 1) {
-                    final Key.Spacer spacer = new Key.Spacer(params, params.mDivider, x + width, y,
+                    final Divider divider = new Divider(params, params.mDivider, x + width, y,
                             params.mDividerWidth, params.mDefaultRowHeight);
-                    params.onAddKey(spacer);
+                    params.onAddKey(divider);
                 }
             }
             return new MoreSuggestions(params);
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
similarity index 79%
rename from java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index c61dd63..e64e7a6 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -34,6 +34,7 @@
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.R;
 
 /**
  * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
@@ -42,30 +43,30 @@
 public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
     private final int[] mCoordinates = new int[2];
 
-    private final KeyDetector mModalPanelKeyDetector;
+    final KeyDetector mModalPanelKeyDetector;
     private final KeyDetector mSlidingPanelKeyDetector;
 
     private Controller mController;
-    private KeyboardActionListener mListener;
+    KeyboardActionListener mListener;
     private int mOriginX;
     private int mOriginY;
 
-    private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter();
+    static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter();
 
-    private final KeyboardActionListener mSuggestionsPaneListener =
+    final KeyboardActionListener mSuggestionsPaneListener =
             new KeyboardActionListener.Adapter() {
         @Override
-        public void onPress(int primaryCode, boolean withSliding) {
-            mListener.onPress(primaryCode, withSliding);
+        public void onPressKey(int primaryCode) {
+            mListener.onPressKey(primaryCode);
         }
 
         @Override
-        public void onRelease(int primaryCode, boolean withSliding) {
-            mListener.onRelease(primaryCode, withSliding);
+        public void onReleaseKey(int primaryCode, boolean withSliding) {
+            mListener.onReleaseKey(primaryCode, withSliding);
         }
 
         @Override
-        public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+        public void onCodeInput(int primaryCode, int x, int y) {
             final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE;
             if (index >= 0 && index < SuggestionsView.MAX_SUGGESTIONS) {
                 mListener.onCustomRequest(index);
@@ -140,43 +141,28 @@
     }
 
     @Override
-    public void setShifted(boolean shifted) {
-        // Nothing to do with.
-    }
-
-    @Override
     public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY,
             PopupWindow window, KeyboardActionListener listener) {
         mController = controller;
         mListener = listener;
         final View container = (View)getParent();
         final MoreSuggestions pane = (MoreSuggestions)getKeyboard();
-
-        parentView.getLocationInWindow(mCoordinates);
-        final int paneLeft = pointX - (pane.mOccupiedWidth / 2) + parentView.getPaddingLeft();
-        final int x = wrapUp(Math.max(0, Math.min(paneLeft,
-                parentView.getWidth() - pane.mOccupiedWidth))
-                - container.getPaddingLeft() + mCoordinates[0],
-                container.getMeasuredWidth(), 0, parentView.getWidth());
-        final int y = pointY
-                - (container.getMeasuredHeight() - container.getPaddingBottom())
-                + parentView.getPaddingTop() + mCoordinates[1];
+        final int defaultCoordX = pane.mOccupiedWidth / 2;
+        // The coordinates of panel's left-top corner in parentView's coordinate system.
+        final int x = pointX - defaultCoordX - container.getPaddingLeft()
+                + parentView.getPaddingLeft();
+        final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
+                + parentView.getPaddingTop();
 
         window.setContentView(container);
         window.setWidth(container.getMeasuredWidth());
         window.setHeight(container.getMeasuredHeight());
-        window.showAtLocation(parentView, Gravity.NO_GRAVITY, x, y);
+        parentView.getLocationInWindow(mCoordinates);
+        window.showAtLocation(parentView, Gravity.NO_GRAVITY,
+                x + mCoordinates[0], y + mCoordinates[1]);
 
-        mOriginX = x + container.getPaddingLeft() - mCoordinates[0];
-        mOriginY = y + container.getPaddingTop() - mCoordinates[1];
-    }
-
-    private static int wrapUp(int x, int width, int left, int right) {
-        if (x < left)
-            return left;
-        if (x + width > right)
-            return right - width;
-        return x;
+        mOriginX = x + container.getPaddingLeft();
+        mOriginY = y + container.getPaddingTop();
     }
 
     private boolean mIsDismissing;
diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
similarity index 78%
rename from java/src/com/android/inputmethod/latin/SuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
index 0986a0b..1ad37b9 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -30,7 +30,6 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Message;
-import android.os.SystemClock;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -53,15 +52,19 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
-import com.android.inputmethod.compat.FrameLayoutCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.keyboard.ViewLayoutUtils;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Utils;
 
 import java.util.ArrayList;
-import java.util.List;
 
 public class SuggestionsView extends RelativeLayout implements OnClickListener,
         OnLongClickListener {
@@ -73,7 +76,7 @@
     // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
     public static final int MAX_SUGGESTIONS = 18;
 
-    private static final boolean DBG = LatinImeLogger.sDBG;
+    static final boolean DBG = LatinImeLogger.sDBG;
 
     private final ViewGroup mSuggestionsStrip;
     private KeyboardView mKeyboardView;
@@ -91,7 +94,7 @@
     private final TextView mPreviewText;
 
     private Listener mListener;
-    private SuggestedWords mSuggestions = SuggestedWords.EMPTY;
+    private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
 
     private final SuggestionsViewParams mParams;
     private static final float MIN_TEXT_XSCALE = 0.70f;
@@ -101,8 +104,6 @@
     private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionsView> {
         private static final int MSG_HIDE_PREVIEW = 0;
 
-        private static final long DELAY_HIDE_PREVIEW = 1300;
-
         public UiHandler(SuggestionsView outerInstance) {
             super(outerInstance);
         }
@@ -117,11 +118,6 @@
             }
         }
 
-        public void postHidePreview() {
-            cancelHidePreview();
-            sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW);
-        }
-
         public void cancelHidePreview() {
             removeMessages(MSG_HIDE_PREVIEW);
         }
@@ -146,10 +142,11 @@
         public final float mMinMoreSuggestionsWidth;
         public final int mMoreSuggestionsBottomGap;
 
-        private final List<TextView> mWords;
-        private final List<View> mDividers;
-        private final List<TextView> mInfos;
+        private final ArrayList<TextView> mWords;
+        private final ArrayList<View> mDividers;
+        private final ArrayList<TextView> mInfos;
 
+        private final int mColorValidTypedWord;
         private final int mColorTypedWord;
         private final int mColorAutoCorrect;
         private final int mColorSuggested;
@@ -158,6 +155,7 @@
         private final int mCenterSuggestionIndex;
         private final Drawable mMoreSuggestionsHint;
         private static final String MORE_SUGGESTIONS_HINT = "\u2026";
+        private static final String LEFTWARDS_ARROW = "\u2190";
 
         private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
         private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
@@ -172,11 +170,11 @@
         public boolean mMoreSuggestionsAvailable;
 
         public final TextView mWordToSaveView;
+        private final TextView mLeftwardsArrowView;
         private final TextView mHintToSaveView;
-        private final CharSequence mHintToSaveText;
 
         public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle,
-                List<TextView> words, List<View> dividers, List<TextView> infos) {
+                ArrayList<TextView> words, ArrayList<View> dividers, ArrayList<TextView> infos) {
             mWords = words;
             mDividers = dividers;
             mInfos = infos;
@@ -194,6 +192,8 @@
             final TypedArray a = context.obtainStyledAttributes(
                     attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle);
             mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0);
+            final float alphaValidTypedWord = getPercent(a,
+                    R.styleable.SuggestionsView_alphaValidTypedWord, 100);
             final float alphaTypedWord = getPercent(a,
                     R.styleable.SuggestionsView_alphaTypedWord, 100);
             final float alphaAutoCorrect = getPercent(a,
@@ -201,6 +201,9 @@
             final float alphaSuggested = getPercent(a,
                     R.styleable.SuggestionsView_alphaSuggested, 100);
             mAlphaObsoleted = getPercent(a, R.styleable.SuggestionsView_alphaSuggested, 100);
+            mColorValidTypedWord = applyAlpha(
+                    a.getColor(R.styleable.SuggestionsView_colorValidTypedWord, 0),
+                    alphaValidTypedWord);
             mColorTypedWord = applyAlpha(
                     a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0), alphaTypedWord);
             mColorAutoCorrect = applyAlpha(
@@ -230,8 +233,8 @@
 
             final LayoutInflater inflater = LayoutInflater.from(context);
             mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
-            mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
-            mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
+            mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+            mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
         }
 
         public int getMaxMoreSuggestionsRow() {
@@ -281,10 +284,10 @@
             return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
         }
 
-        private CharSequence getStyledSuggestionWord(SuggestedWords suggestions, int pos) {
-            final CharSequence word = suggestions.getWord(pos);
-            final boolean isAutoCorrect = pos == 1 && Utils.willAutoCorrect(suggestions);
-            final boolean isTypedWordValid = pos == 0 && suggestions.mTypedWordValid;
+        private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
+            final CharSequence word = suggestedWords.getWord(pos);
+            final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
+            final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
             if (!isAutoCorrect && !isTypedWordValid)
                 return word;
 
@@ -301,10 +304,10 @@
             return spannedWord;
         }
 
-        private int getWordPosition(int index, SuggestedWords suggestions) {
+        private int getWordPosition(int index, SuggestedWords suggestedWords) {
             // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
             // suggestions.
-            final int centerPos = Utils.willAutoCorrect(suggestions) ? 1 : 0;
+            final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
             if (index == mCenterSuggestionIndex) {
                 return centerPos;
             } else if (index == centerPos) {
@@ -314,28 +317,31 @@
             }
         }
 
-        private int getSuggestionTextColor(int index, SuggestedWords suggestions, int pos) {
+        private int getSuggestionTextColor(int index, SuggestedWords suggestedWords, int pos) {
             // TODO: Need to revisit this logic with bigram suggestions
             final boolean isSuggested = (pos != 0);
 
             final int color;
-            if (index == mCenterSuggestionIndex && Utils.willAutoCorrect(suggestions)) {
+            if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
                 color = mColorAutoCorrect;
+            } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
+                color = mColorValidTypedWord;
             } else if (isSuggested) {
                 color = mColorSuggested;
             } else {
                 color = mColorTypedWord;
             }
-            if (LatinImeLogger.sDBG) {
-                if (index == mCenterSuggestionIndex && suggestions.mHasAutoCorrectionCandidate
-                        && suggestions.shouldBlockAutoCorrection()) {
+            if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
+                // If we auto-correct, then the autocorrection is in slot 0 and the typed word
+                // is in slot 1.
+                if (index == mCenterSuggestionIndex && suggestedWords.mHasAutoCorrectionCandidate
+                        && Suggest.shouldBlockAutoCorrectionBySafetyNet(
+                                suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) {
                     return 0xFFFF0000;
                 }
             }
 
-            final SuggestedWordInfo info = (pos < suggestions.size())
-                    ? suggestions.getInfo(pos) : null;
-            if (info != null && info.isObsoleteSuggestedWord()) {
+            if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
                 return applyAlpha(color, mAlphaObsoleted);
             } else {
                 return color;
@@ -354,19 +360,19 @@
             params.gravity = Gravity.CENTER;
         }
 
-        public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer,
+        public void layout(SuggestedWords suggestedWords, ViewGroup stripView, ViewGroup placer,
                 int stripWidth) {
-            if (suggestions.isPunctuationSuggestions()) {
-                layoutPunctuationSuggestions(suggestions, stripView);
+            if (suggestedWords.mIsPunctuationSuggestions) {
+                layoutPunctuationSuggestions(suggestedWords, stripView);
                 return;
             }
 
             final int countInStrip = mSuggestionsCountInStrip;
-            setupTexts(suggestions, countInStrip);
-            mMoreSuggestionsAvailable = (suggestions.size() > countInStrip);
+            setupTexts(suggestedWords, countInStrip);
+            mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
             int x = 0;
             for (int index = 0; index < countInStrip; index++) {
-                final int pos = getWordPosition(index, suggestions);
+                final int pos = getWordPosition(index, suggestedWords);
 
                 if (index != 0) {
                     final View divider = mDividers.get(pos);
@@ -389,7 +395,7 @@
 
                 // Disable this suggestion if the suggestion is null or empty.
                 word.setEnabled(!TextUtils.isEmpty(styled));
-                word.setTextColor(getSuggestionTextColor(index, suggestions, pos));
+                word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos));
                 final int width = getSuggestionWidth(index, stripWidth);
                 final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
                 final float scaleX = word.getTextScaleX();
@@ -400,8 +406,8 @@
                         word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT);
                 x += word.getMeasuredWidth();
 
-                if (DBG) {
-                    final CharSequence debugInfo = getDebugInfo(suggestions, pos);
+                if (DBG && pos < suggestedWords.size()) {
+                    final CharSequence debugInfo = Utils.getDebugInfo(suggestedWords, pos);
                     if (debugInfo != null) {
                         final TextView info = mInfos.get(pos);
                         info.setText(debugInfo);
@@ -410,7 +416,7 @@
                                 ViewGroup.LayoutParams.WRAP_CONTENT);
                         final int infoWidth = info.getMeasuredWidth();
                         final int y = info.getMeasuredHeight();
-                        FrameLayoutCompatUtils.placeViewAt(
+                        ViewLayoutUtils.placeViewAt(
                                 info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
                     }
                 }
@@ -433,11 +439,11 @@
             }
         }
 
-        private void setupTexts(SuggestedWords suggestions, int countInStrip) {
+        private void setupTexts(SuggestedWords suggestedWords, int countInStrip) {
             mTexts.clear();
-            final int count = Math.min(suggestions.size(), countInStrip);
+            final int count = Math.min(suggestedWords.size(), countInStrip);
             for (int pos = 0; pos < count; pos++) {
-                final CharSequence styled = getStyledSuggestionWord(suggestions, pos);
+                final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos);
                 mTexts.add(styled);
             }
             for (int pos = count; pos < countInStrip; pos++) {
@@ -446,8 +452,9 @@
             }
         }
 
-        private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) {
-            final int countInStrip = Math.min(suggestions.size(), PUNCTUATIONS_IN_STRIP);
+        private void layoutPunctuationSuggestions(SuggestedWords suggestedWords,
+                ViewGroup stripView) {
+            final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
             for (int index = 0; index < countInStrip; index++) {
                 if (index != 0) {
                     // Add divider if this isn't the left most suggestion in suggestions strip.
@@ -456,8 +463,8 @@
 
                 final TextView word = mWords.get(index);
                 word.setEnabled(true);
-                word.setTextColor(mColorTypedWord);
-                final CharSequence text = suggestions.getWord(index);
+                word.setTextColor(mColorAutoCorrect);
+                final CharSequence text = suggestedWords.getWord(index);
                 word.setText(text);
                 word.setTextScaleX(1.0f);
                 word.setCompoundDrawables(null, null, null, null);
@@ -468,7 +475,7 @@
         }
 
         public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
-                int stripWidth) {
+                int stripWidth, CharSequence hintText) {
             final int width = stripWidth - mDividerWidth - mPadding * 2;
 
             final TextView wordView = mWordToSaveView;
@@ -484,16 +491,94 @@
 
             stripView.addView(mDividers.get(0));
 
+            final TextView leftArrowView = mLeftwardsArrowView;
+            leftArrowView.setTextColor(mColorAutoCorrect);
+            leftArrowView.setText(LEFTWARDS_ARROW);
+            stripView.addView(leftArrowView);
+
             final TextView hintView = mHintToSaveView;
+            hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
             hintView.setTextColor(mColorAutoCorrect);
-            final int hintWidth = width - wordWidth;
-            final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint());
-            hintView.setText(mHintToSaveText);
+            final int hintWidth = width - wordWidth - leftArrowView.getWidth();
+            final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
+            hintView.setText(hintText);
             hintView.setTextScaleX(hintScaleX);
             stripView.addView(hintView);
             setLayoutWeight(
                     hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
         }
+
+        private static void setLayoutWeight(View v, float weight, int height) {
+            final ViewGroup.LayoutParams lp = v.getLayoutParams();
+            if (lp instanceof LinearLayout.LayoutParams) {
+                final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+                llp.weight = weight;
+                llp.width = 0;
+                llp.height = height;
+            }
+        }
+
+        private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
+            paint.setTextScaleX(1.0f);
+            final int width = getTextWidth(text, paint);
+            if (width <= maxWidth) {
+                return 1.0f;
+            }
+            return maxWidth / (float)width;
+        }
+
+        private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
+                TextPaint paint) {
+            if (text == null) return null;
+            paint.setTextScaleX(1.0f);
+            final int width = getTextWidth(text, paint);
+            if (width <= maxWidth) {
+                return text;
+            }
+            final float scaleX = maxWidth / (float)width;
+            if (scaleX >= MIN_TEXT_XSCALE) {
+                paint.setTextScaleX(scaleX);
+                return text;
+            }
+
+            // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
+            // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+            final CharSequence ellipsized = TextUtils.ellipsize(
+                    text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
+            paint.setTextScaleX(MIN_TEXT_XSCALE);
+            return ellipsized;
+        }
+
+        private static int getTextWidth(CharSequence text, TextPaint paint) {
+            if (TextUtils.isEmpty(text)) return 0;
+            final Typeface savedTypeface = paint.getTypeface();
+            paint.setTypeface(getTextTypeface(text));
+            final int len = text.length();
+            final float[] widths = new float[len];
+            final int count = paint.getTextWidths(text, 0, len, widths);
+            int width = 0;
+            for (int i = 0; i < count; i++) {
+                width += Math.round(widths[i] + 0.5f);
+            }
+            paint.setTypeface(savedTypeface);
+            return width;
+        }
+
+        private static Typeface getTextTypeface(CharSequence text) {
+            if (!(text instanceof SpannableString))
+                return Typeface.DEFAULT;
+
+            final SpannableString ss = (SpannableString)text;
+            final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
+            if (styles.length == 0)
+                return Typeface.DEFAULT;
+
+            switch (styles[0].getStyle()) {
+            case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
+            // TODO: BOLD_ITALIC, ITALIC case?
+            default: return Typeface.DEFAULT;
+            }
+        }
     }
 
     /**
@@ -571,98 +656,13 @@
         mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
     }
 
-    public void setSuggestions(SuggestedWords suggestions) {
-        if (suggestions == null || suggestions.size() == 0)
+    public void setSuggestions(SuggestedWords suggestedWords) {
+        if (suggestedWords == null || suggestedWords.size() == 0)
             return;
 
         clear();
-        mSuggestions = suggestions;
-        mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth());
-    }
-
-    private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
-        if (DBG && pos < suggestions.size()) {
-            final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-            if (wordInfo != null) {
-                final CharSequence debugInfo = wordInfo.getDebugString();
-                if (!TextUtils.isEmpty(debugInfo)) {
-                    return debugInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    private static void setLayoutWeight(View v, float weight, int height) {
-        final ViewGroup.LayoutParams lp = v.getLayoutParams();
-        if (lp instanceof LinearLayout.LayoutParams) {
-            final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
-            llp.weight = weight;
-            llp.width = 0;
-            llp.height = height;
-        }
-    }
-
-    private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
-        paint.setTextScaleX(1.0f);
-        final int width = getTextWidth(text, paint);
-        if (width <= maxWidth) {
-            return 1.0f;
-        }
-        return maxWidth / (float)width;
-    }
-
-    private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
-            TextPaint paint) {
-        if (text == null) return null;
-        paint.setTextScaleX(1.0f);
-        final int width = getTextWidth(text, paint);
-        if (width <= maxWidth) {
-            return text;
-        }
-        final float scaleX = maxWidth / (float)width;
-        if (scaleX >= MIN_TEXT_XSCALE) {
-            paint.setTextScaleX(scaleX);
-            return text;
-        }
-
-        // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To get
-        // squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
-        final CharSequence ellipsized = TextUtils.ellipsize(
-                text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
-        paint.setTextScaleX(MIN_TEXT_XSCALE);
-        return ellipsized;
-    }
-
-    private static int getTextWidth(CharSequence text, TextPaint paint) {
-        if (TextUtils.isEmpty(text)) return 0;
-        final Typeface savedTypeface = paint.getTypeface();
-        paint.setTypeface(getTextTypeface(text));
-        final int len = text.length();
-        final float[] widths = new float[len];
-        final int count = paint.getTextWidths(text, 0, len, widths);
-        int width = 0;
-        for (int i = 0; i < count; i++) {
-            width += Math.round(widths[i] + 0.5f);
-        }
-        paint.setTypeface(savedTypeface);
-        return width;
-    }
-
-    private static Typeface getTextTypeface(CharSequence text) {
-        if (!(text instanceof SpannableString))
-            return Typeface.DEFAULT;
-
-        final SpannableString ss = (SpannableString)text;
-        final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
-        if (styles.length == 0)
-            return Typeface.DEFAULT;
-
-        switch (styles[0].getStyle()) {
-        case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
-        // TODO: BOLD_ITALIC, ITALIC case?
-        default: return Typeface.DEFAULT;
-        }
+        mSuggestedWords = suggestedWords;
+        mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
     }
 
     public int setMoreSuggestionsHeight(int remainingHeight) {
@@ -674,9 +674,9 @@
                 && mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView;
     }
 
-    public void showAddToDictionaryHint(CharSequence word) {
+    public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
         clear();
-        mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth());
+        mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText);
     }
 
     public boolean dismissAddToDictionaryHint() {
@@ -688,7 +688,7 @@
     }
 
     public SuggestedWords getSuggestions() {
-        return mSuggestions;
+        return mSuggestedWords;
     }
 
     public void clear() {
@@ -702,34 +702,8 @@
         mPreviewPopup.dismiss();
     }
 
-    private void showPreview(View view, CharSequence word) {
-        if (TextUtils.isEmpty(word))
-            return;
-
-        final TextView previewText = mPreviewText;
-        previewText.setTextColor(mParams.mColorTypedWord);
-        previewText.setText(word);
-        previewText.measure(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        final int[] offsetInWindow = new int[2];
-        view.getLocationInWindow(offsetInWindow);
-        final int posX = offsetInWindow[0];
-        final int posY = offsetInWindow[1] - previewText.getMeasuredHeight();
-        final PopupWindow previewPopup = mPreviewPopup;
-        if (previewPopup.isShowing()) {
-            previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight());
-        } else {
-            previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY);
-        }
-        previewText.setVisibility(VISIBLE);
-        mHandler.postHidePreview();
-    }
-
     private void addToDictionary(CharSequence word) {
-        if (mListener.addWordToDictionary(word.toString())) {
-            final CharSequence message = getContext().getString(R.string.added_word, word);
-            showPreview(mParams.mWordToSaveView, message);
-        }
+        mListener.addWordToDictionary(word.toString());
     }
 
     private final KeyboardActionListener mMoreSuggestionsListener =
@@ -737,7 +711,7 @@
         @Override
         public boolean onCustomRequest(int requestCode) {
             final int index = requestCode;
-            final CharSequence word = mSuggestions.getWord(index);
+            final CharSequence word = mSuggestedWords.getWord(index);
             mListener.pickSuggestionManually(index, word);
             dismissMoreSuggestions();
             return true;
@@ -782,7 +756,7 @@
             final int maxWidth = stripWidth - container.getPaddingLeft()
                     - container.getPaddingRight();
             final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
-            builder.layout(mSuggestions, params.mSuggestionsCountInStrip, maxWidth,
+            builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
                     (int)(maxWidth * params.mMinMoreSuggestionsWidth),
                     params.getMaxMoreSuggestionsRow());
             mMoreSuggestionsView.setKeyboard(builder.build());
@@ -859,8 +833,7 @@
                 // Decided to be in the sliding input mode only when the touch point has been moved
                 // upward.
                 mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
-                tracker.onShowMoreKeysPanel(
-                        translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+                tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
             } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
                 // Decided to be in the modal input mode
                 mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
@@ -885,10 +858,10 @@
         if (!(tag instanceof Integer))
             return;
         final int index = (Integer) tag;
-        if (index >= mSuggestions.size())
+        if (index >= mSuggestedWords.size())
             return;
 
-        final CharSequence word = mSuggestions.getWord(index);
+        final CharSequence word = mSuggestedWords.getWord(index);
         mListener.pickSuggestionManually(index, word);
     }
 
diff --git a/native/Android.mk b/native/Android.mk
index f07be6a..5053e7d 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -1,60 +1 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
-
-LOCAL_CFLAGS += -Werror -Wall
-
-# To suppress compiler warnings for unused variables/functions used for debug features etc.
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
-
-LOCAL_SRC_FILES := \
-    jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \
-    jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
-    jni/jni_common.cpp \
-    src/bigram_dictionary.cpp \
-    src/char_utils.cpp \
-    src/correction.cpp \
-    src/dictionary.cpp \
-    src/proximity_info.cpp \
-    src/unigram_dictionary.cpp
-
-#FLAG_DBG := true
-#FLAG_DO_PROFILE := true
-
-TARGETING_UNBUNDLED_FROYO := true
-
-ifeq ($(TARGET_ARCH), x86)
-    TARGETING_UNBUNDLED_FROYO := false
-endif
-
-ifeq ($(FLAG_DBG), true)
-    TARGETING_UNBUNDLED_FROYO := false
-endif
-
-ifeq ($(FLAG_DO_PROFILE), true)
-    TARGETING_UNBUNDLED_FROYO := false
-endif
-
-ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
-    LOCAL_NDK_VERSION := 4
-    LOCAL_SDK_VERSION := 8
-endif
-
-LOCAL_MODULE := libjni_latinime
-
-LOCAL_MODULE_TAGS := user
-
-ifeq ($(FLAG_DO_PROFILE), true)
-    $(warning Making profiling version of native library)
-    LOCAL_CFLAGS += -DFLAG_DO_PROFILE
-    LOCAL_SHARED_LIBRARIES := libcutils libutils
-else # FLAG_DO_PROFILE
-ifeq ($(FLAG_DBG), true)
-    $(warning Making debug version of native library)
-    LOCAL_CFLAGS += -DFLAG_DBG
-    LOCAL_SHARED_LIBRARIES := libcutils libutils
-endif # FLAG_DBG
-endif # FLAG_DO_PROFILE
-
-include $(BUILD_SHARED_LIBRARY)
+include $(call all-subdir-makefiles)
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
new file mode 100644
index 0000000..5e0d351
--- /dev/null
+++ b/native/jni/Android.mk
@@ -0,0 +1,138 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+############ some local flags
+# If you change any of those flags, you need to rebuild both libjni_latinime_static
+# and the shared library.
+#FLAG_DBG := true
+#FLAG_DO_PROFILE := true
+
+TARGETING_UNBUNDLED_FROYO := true
+
+ifeq ($(TARGET_ARCH), x86)
+    TARGETING_UNBUNDLED_FROYO := false
+endif
+
+ifeq ($(TARGET_ARCH), mips)
+    TARGETING_UNBUNDLED_FROYO := false
+endif
+
+ifeq ($(FLAG_DBG), true)
+    TARGETING_UNBUNDLED_FROYO := false
+endif
+
+ifeq ($(FLAG_DO_PROFILE), true)
+    TARGETING_UNBUNDLED_FROYO := false
+endif
+
+######################################
+include $(CLEAR_VARS)
+
+LATIN_IME_SRC_DIR := src
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
+
+LOCAL_CFLAGS += -Werror -Wall
+
+# To suppress compiler warnings for unused variables/functions used for debug features etc.
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
+
+LATIN_IME_JNI_SRC_FILES := \
+    com_android_inputmethod_keyboard_ProximityInfo.cpp \
+    com_android_inputmethod_latin_BinaryDictionary.cpp \
+    jni_common.cpp
+
+LATIN_IME_CORE_SRC_FILES := \
+    additional_proximity_chars.cpp \
+    basechars.cpp \
+    bigram_dictionary.cpp \
+    char_utils.cpp \
+    correction.cpp \
+    dictionary.cpp \
+    proximity_info.cpp \
+    unigram_dictionary.cpp
+
+LOCAL_SRC_FILES := \
+    $(LATIN_IME_JNI_SRC_FILES) \
+    $(addprefix $(LATIN_IME_SRC_DIR)/,$(LATIN_IME_CORE_SRC_FILES))
+
+ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
+    LOCAL_NDK_VERSION := 4
+    LOCAL_SDK_VERSION := 8
+endif
+
+ifeq ($(FLAG_DO_PROFILE), true)
+    $(warning Making profiling version of native library)
+    LOCAL_CFLAGS += -DFLAG_DO_PROFILE
+else # FLAG_DO_PROFILE
+ifeq ($(FLAG_DBG), true)
+    $(warning Making debug version of native library)
+    LOCAL_CFLAGS += -DFLAG_DBG
+endif # FLAG_DBG
+endif # FLAG_DO_PROFILE
+
+LOCAL_MODULE := libjni_latinime_static
+LOCAL_MODULE_TAGS := optional
+
+ifdef HISTORICAL_NDK_VERSIONS_ROOT # In the platform build system
+include external/stlport/libstlport.mk
+else # In the NDK build system
+LOCAL_C_INCLUDES += external/stlport/stlport bionic
+endif
+
+include $(BUILD_STATIC_LIBRARY)
+
+######################################
+include $(CLEAR_VARS)
+
+# All code in LOCAL_WHOLE_STATIC_LIBRARIES will be built into this shared library.
+LOCAL_WHOLE_STATIC_LIBRARIES := libjni_latinime_static
+
+ifdef HISTORICAL_NDK_VERSIONS_ROOT # In the platform build system
+LOCAL_SHARED_LIBRARIES := libstlport
+else # In the NDK build system
+LOCAL_SHARED_LIBRARIES := libstlport_static
+endif
+
+ifeq ($(FLAG_DO_PROFILE), true)
+    $(warning Making profiling version of native library)
+    LOCAL_SHARED_LIBRARIES += libcutils libutils
+else # FLAG_DO_PROFILE
+ifeq ($(FLAG_DBG), true)
+    $(warning Making debug version of native library)
+    LOCAL_SHARED_LIBRARIES += libcutils libutils
+endif # FLAG_DBG
+endif # FLAG_DO_PROFILE
+
+ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
+    LOCAL_NDK_VERSION := 4
+    LOCAL_SDK_VERSION := 8
+endif
+
+LOCAL_MODULE := libjni_latinime
+LOCAL_MODULE_TAGS := optional
+
+ifdef HISTORICAL_NDK_VERSIONS_ROOT # In the platform build system
+include external/stlport/libstlport.mk
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+#################### Clean up the tmp vars
+LATIN_IME_CORE_SRC_FILES :=
+LATIN_IME_JNI_SRC_FILES :=
+LATIN_IME_SRC_DIR :=
+TARGETING_UNBUNDLED_FROYO :=
diff --git a/native/jni/Application.mk b/native/jni/Application.mk
new file mode 100644
index 0000000..caf3b26
--- /dev/null
+++ b/native/jni/Application.mk
@@ -0,0 +1 @@
+APP_STL := stlport_static
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 595ea2f..9eb437c 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -25,17 +25,20 @@
 #include <assert.h>
 #include <errno.h>
 #include <stdio.h>
+#include <string>
 
 namespace latinime {
 
-static jint latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
-        jint maxProximityCharsSize, jint displayWidth, jint displayHeight, jint gridWidth,
-        jint gridHeight, jintArray proximityCharsArray, jint keyCount,
-        jintArray keyXCoordinateArray, jintArray keyYCoordinateArray, jintArray keyWidthArray,
-        jintArray keyHeightArray, jintArray keyCharCodeArray,
+static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
+        jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
+        jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
+        jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
+        jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
         jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
         jfloatArray sweetSpotRadiusArray) {
-    jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, NULL);
+    const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
+    const std::string localeStr(localeStrPtr);
+    jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
     jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
     jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
     jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
@@ -44,8 +47,10 @@
     jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
     jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
     jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
-    ProximityInfo *proximityInfo = new ProximityInfo(maxProximityCharsSize, displayWidth,
-            displayHeight, gridWidth, gridHeight, (const uint32_t*)proximityChars,
+    ProximityInfo *proximityInfo = new ProximityInfo(
+            localeStr, maxProximityCharsSize, displayWidth,
+            displayHeight, gridWidth, gridHeight, mostCommonkeyWidth,
+            (const int32_t*)proximityChars,
             keyCount, (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
             (const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
             (const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
@@ -59,19 +64,20 @@
     safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
     safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
     env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
-    return (jint)proximityInfo;
+    env->ReleaseStringUTFChars(localejStr, localeStrPtr);
+    return (jlong)proximityInfo;
 }
 
-static void latinime_Keyboard_release(JNIEnv *env, jobject object, jint proximityInfo) {
+static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
     ProximityInfo *pi = (ProximityInfo*)proximityInfo;
     if (!pi) return;
     delete pi;
 }
 
 static JNINativeMethod sKeyboardMethods[] = {
-    {"setProximityInfoNative", "(IIIII[II[I[I[I[I[I[F[F[F)I",
+    {"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
             (void*)latinime_Keyboard_setProximityInfo},
-    {"releaseProximityInfoNative", "(I)V", (void*)latinime_Keyboard_release}
+    {"releaseProximityInfoNative", "(J)V", (void*)latinime_Keyboard_release}
 };
 
 int register_ProximityInfo(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 18c9724..20e44c2 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "LatinIME: jni: BinaryDictionary"
 
 #include "binary_format.h"
+#include "correction.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 #include "dictionary.h"
 #include "jni.h"
@@ -33,6 +34,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#include <unistd.h>
 #else // USE_MMAP_FOR_DICTIONARY
 #include <stdlib.h>
 #endif // USE_MMAP_FOR_DICTIONARY
@@ -41,75 +43,74 @@
 
 void releaseDictBuf(void* dictBuf, const size_t length, int fd);
 
-static jint latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
+static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
         jstring sourceDir, jlong dictOffset, jlong dictSize,
-        jint typedLetterMultiplier, jint fullWordMultiplier, jint maxWordLength, jint maxWords,
-        jint maxAlternatives) {
+        jint typedLetterMultiplier, jint fullWordMultiplier, jint maxWordLength, jint maxWords) {
     PROF_OPEN;
     PROF_START(66);
-    const char *sourceDirChars = env->GetStringUTFChars(sourceDir, NULL);
-    if (sourceDirChars == NULL) {
-        LOGE("DICT: Can't get sourceDir string");
+    const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
+    if (sourceDirChars == 0) {
+        AKLOGE("DICT: Can't get sourceDir string");
         return 0;
     }
     int fd = 0;
-    void *dictBuf = NULL;
+    void *dictBuf = 0;
     int adjust = 0;
 #ifdef USE_MMAP_FOR_DICTIONARY
     /* mmap version */
     fd = open(sourceDirChars, O_RDONLY);
     if (fd < 0) {
-        LOGE("DICT: Can't open sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
+        AKLOGE("DICT: Can't open sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
         return 0;
     }
     int pagesize = getpagesize();
     adjust = dictOffset % pagesize;
     int adjDictOffset = dictOffset - adjust;
     int adjDictSize = dictSize + adjust;
-    dictBuf = mmap(NULL, sizeof(char) * adjDictSize, PROT_READ, MAP_PRIVATE, fd, adjDictOffset);
+    dictBuf = mmap(0, sizeof(char) * adjDictSize, PROT_READ, MAP_PRIVATE, fd, adjDictOffset);
     if (dictBuf == MAP_FAILED) {
-        LOGE("DICT: Can't mmap dictionary. errno=%d", errno);
+        AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
         return 0;
     }
     dictBuf = (void *)((char *)dictBuf + adjust);
 #else // USE_MMAP_FOR_DICTIONARY
     /* malloc version */
-    FILE *file = NULL;
+    FILE *file = 0;
     file = fopen(sourceDirChars, "rb");
-    if (file == NULL) {
-        LOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
+    if (file == 0) {
+        AKLOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
         return 0;
     }
     dictBuf = malloc(sizeof(char) * dictSize);
     if (!dictBuf) {
-        LOGE("DICT: Can't allocate memory region for dictionary. errno=%d", errno);
+        AKLOGE("DICT: Can't allocate memory region for dictionary. errno=%d", errno);
         return 0;
     }
     int ret = fseek(file, (long)dictOffset, SEEK_SET);
     if (ret != 0) {
-        LOGE("DICT: Failure in fseek. ret=%d errno=%d", ret, errno);
+        AKLOGE("DICT: Failure in fseek. ret=%d errno=%d", ret, errno);
         return 0;
     }
     ret = fread(dictBuf, sizeof(char) * dictSize, 1, file);
     if (ret != 1) {
-        LOGE("DICT: Failure in fread. ret=%d errno=%d", ret, errno);
+        AKLOGE("DICT: Failure in fread. ret=%d errno=%d", ret, errno);
         return 0;
     }
     ret = fclose(file);
     if (ret != 0) {
-        LOGE("DICT: Failure in fclose. ret=%d errno=%d", ret, errno);
+        AKLOGE("DICT: Failure in fclose. ret=%d errno=%d", ret, errno);
         return 0;
     }
 #endif // USE_MMAP_FOR_DICTIONARY
     env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
 
     if (!dictBuf) {
-        LOGE("DICT: dictBuf is null");
+        AKLOGE("DICT: dictBuf is null");
         return 0;
     }
-    Dictionary *dictionary = NULL;
+    Dictionary *dictionary = 0;
     if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
-        LOGE("DICT: dictionary format is unknown, bad magic number");
+        AKLOGE("DICT: dictionary format is unknown, bad magic number");
 #ifdef USE_MMAP_FOR_DICTIONARY
         releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
 #else // USE_MMAP_FOR_DICTIONARY
@@ -117,27 +118,27 @@
 #endif // USE_MMAP_FOR_DICTIONARY
     } else {
         dictionary = new Dictionary(dictBuf, dictSize, fd, adjust, typedLetterMultiplier,
-                fullWordMultiplier, maxWordLength, maxWords, maxAlternatives);
+                fullWordMultiplier, maxWordLength, maxWords);
     }
     PROF_END(66);
     PROF_CLOSE;
-    return (jint)dictionary;
+    return (jlong)dictionary;
 }
 
-static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jint dict,
-        jint proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
+static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
+        jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
         jintArray inputArray, jint arraySize, jint flags,
         jcharArray outputArray, jintArray frequencyArray) {
     Dictionary *dictionary = (Dictionary*)dict;
     if (!dictionary) return 0;
     ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
 
-    int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, NULL);
-    int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, NULL);
+    int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
+    int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
 
-    int *frequencies = env->GetIntArrayElements(frequencyArray, NULL);
-    int *inputCodes = env->GetIntArrayElements(inputArray, NULL);
-    jchar *outputChars = env->GetCharArrayElements(outputArray, NULL);
+    int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
+    int *inputCodes = env->GetIntArrayElements(inputArray, 0);
+    jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
 
     int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, inputCodes,
             arraySize, flags, (unsigned short*) outputChars, frequencies);
@@ -151,21 +152,19 @@
     return count;
 }
 
-static int latinime_BinaryDictionary_getBigrams(JNIEnv *env, jobject object, jint dict,
+static int latinime_BinaryDictionary_getBigrams(JNIEnv *env, jobject object, jlong dict,
         jcharArray prevWordArray, jint prevWordLength, jintArray inputArray, jint inputArraySize,
-        jcharArray outputArray, jintArray frequencyArray, jint maxWordLength, jint maxBigrams,
-        jint maxAlternatives) {
+        jcharArray outputArray, jintArray frequencyArray, jint maxWordLength, jint maxBigrams) {
     Dictionary *dictionary = (Dictionary*)dict;
     if (!dictionary) return 0;
 
-    jchar *prevWord = env->GetCharArrayElements(prevWordArray, NULL);
-    int *inputCodes = env->GetIntArrayElements(inputArray, NULL);
-    jchar *outputChars = env->GetCharArrayElements(outputArray, NULL);
-    int *frequencies = env->GetIntArrayElements(frequencyArray, NULL);
+    jchar *prevWord = env->GetCharArrayElements(prevWordArray, 0);
+    int *inputCodes = env->GetIntArrayElements(inputArray, 0);
+    jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
+    int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
 
     int count = dictionary->getBigrams((unsigned short*) prevWord, prevWordLength, inputCodes,
-            inputArraySize, (unsigned short*) outputChars, frequencies, maxWordLength, maxBigrams,
-            maxAlternatives);
+            inputArraySize, (unsigned short*) outputChars, frequencies, maxWordLength, maxBigrams);
 
     env->ReleaseCharArrayElements(prevWordArray, prevWord, JNI_ABORT);
     env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
@@ -175,19 +174,42 @@
     return count;
 }
 
-static jboolean latinime_BinaryDictionary_isValidWord(JNIEnv *env, jobject object, jint dict,
+static jboolean latinime_BinaryDictionary_isValidWord(JNIEnv *env, jobject object, jlong dict,
         jcharArray wordArray, jint wordLength) {
     Dictionary *dictionary = (Dictionary*)dict;
     if (!dictionary) return (jboolean) false;
 
-    jchar *word = env->GetCharArrayElements(wordArray, NULL);
+    jchar *word = env->GetCharArrayElements(wordArray, 0);
     jboolean result = dictionary->isValidWord((unsigned short*) word, wordLength);
     env->ReleaseCharArrayElements(wordArray, word, JNI_ABORT);
 
     return result;
 }
 
-static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jint dict) {
+static jdouble latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
+        jcharArray before, jint beforeLength, jcharArray after, jint afterLength, jint score) {
+    jchar *beforeChars = env->GetCharArrayElements(before, 0);
+    jchar *afterChars = env->GetCharArrayElements(after, 0);
+    jdouble result = Correction::RankingAlgorithm::calcNormalizedScore(
+            (unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength,
+                    score);
+    env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
+    env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
+    return result;
+}
+
+static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
+        jcharArray before, jint beforeLength, jcharArray after, jint afterLength) {
+    jchar *beforeChars = env->GetCharArrayElements(before, 0);
+    jchar *afterChars = env->GetCharArrayElements(after, 0);
+    jint result = Correction::RankingAlgorithm::editDistance(
+            (unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength);
+    env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
+    env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
+    return result;
+}
+
+static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
     Dictionary *dictionary = (Dictionary*)dict;
     if (!dictionary) return;
     void *dictBuf = dictionary->getDict();
@@ -205,11 +227,11 @@
 #ifdef USE_MMAP_FOR_DICTIONARY
     int ret = munmap(dictBuf, length);
     if (ret != 0) {
-        LOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
+        AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
     }
     ret = close(fd);
     if (ret != 0) {
-        LOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
+        AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
     }
 #else // USE_MMAP_FOR_DICTIONARY
     free(dictBuf);
@@ -217,11 +239,14 @@
 }
 
 static JNINativeMethod sMethods[] = {
-    {"openNative", "(Ljava/lang/String;JJIIIII)I", (void*)latinime_BinaryDictionary_open},
-    {"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close},
-    {"getSuggestionsNative", "(II[I[I[III[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions},
-    {"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord},
-    {"getBigramsNative", "(I[CI[II[C[IIII)I", (void*)latinime_BinaryDictionary_getBigrams}
+    {"openNative", "(Ljava/lang/String;JJIIII)J", (void*)latinime_BinaryDictionary_open},
+    {"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
+    {"getSuggestionsNative", "(JJ[I[I[III[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions},
+    {"isValidWordNative", "(J[CI)Z", (void*)latinime_BinaryDictionary_isValidWord},
+    {"getBigramsNative", "(J[CI[II[C[III)I", (void*)latinime_BinaryDictionary_getBigrams},
+    {"calcNormalizedScoreNative", "([CI[CII)D",
+            (void*)latinime_BinaryDictionary_calcNormalizedScore},
+    {"editDistanceNative", "([CI[CI)I", (void*)latinime_BinaryDictionary_editDistance}
 };
 
 int register_BinaryDictionary(JNIEnv *env) {
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 8643f72..85d2683 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -32,22 +32,22 @@
  * Returns the JNI version on success, -1 on failure.
  */
 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
-    JNIEnv* env = NULL;
+    JNIEnv* env = 0;
     jint result = -1;
 
     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
-        LOGE("ERROR: GetEnv failed");
+        AKLOGE("ERROR: GetEnv failed");
         goto bail;
     }
-    assert(env != NULL);
+    assert(env != 0);
 
     if (!register_BinaryDictionary(env)) {
-        LOGE("ERROR: BinaryDictionary native registration failed");
+        AKLOGE("ERROR: BinaryDictionary native registration failed");
         goto bail;
     }
 
     if (!register_ProximityInfo(env)) {
-        LOGE("ERROR: ProximityInfo native registration failed");
+        AKLOGE("ERROR: ProximityInfo native registration failed");
         goto bail;
     }
 
@@ -63,12 +63,12 @@
 int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* methods,
         int numMethods) {
     jclass clazz = env->FindClass(className);
-    if (clazz == NULL) {
-        LOGE("Native registration unable to find class '%s'", className);
+    if (clazz == 0) {
+        AKLOGE("Native registration unable to find class '%s'", className);
         return JNI_FALSE;
     }
     if (env->RegisterNatives(clazz, methods, numMethods) < 0) {
-        LOGE("RegisterNatives failed for '%s'", className);
+        AKLOGE("RegisterNatives failed for '%s'", className);
         env->DeleteLocalRef(clazz);
         return JNI_FALSE;
     }
diff --git a/native/jni/jni_common.h b/native/jni/jni_common.h
index 9548e1b..6741443 100644
--- a/native/jni/jni_common.h
+++ b/native/jni/jni_common.h
@@ -29,17 +29,17 @@
 
 inline jint *safeGetIntArrayElements(JNIEnv *env, jintArray jArray) {
     if (jArray) {
-        return env->GetIntArrayElements(jArray, NULL);
+        return env->GetIntArrayElements(jArray, 0);
     } else {
-        return NULL;
+        return 0;
     }
 }
 
 inline jfloat *safeGetFloatArrayElements(JNIEnv *env, jfloatArray jArray) {
     if (jArray) {
-        return env->GetFloatArrayElements(jArray, NULL);
+        return env->GetFloatArrayElements(jArray, 0);
     } else {
-        return NULL;
+        return 0;
     }
 }
 
diff --git a/native/jni/src/additional_proximity_chars.cpp b/native/jni/src/additional_proximity_chars.cpp
new file mode 100644
index 0000000..224f020
--- /dev/null
+++ b/native/jni/src/additional_proximity_chars.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "additional_proximity_chars.h"
+
+namespace latinime {
+const std::string AdditionalProximityChars::LOCALE_EN_US("en");
+
+const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
+    'e', 'i', 'o', 'u'
+};
+
+const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_E[EN_US_ADDITIONAL_E_SIZE] = {
+    'a', 'i', 'o', 'u'
+};
+
+const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_I[EN_US_ADDITIONAL_I_SIZE] = {
+    'a', 'e', 'o', 'u'
+};
+
+const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_O[EN_US_ADDITIONAL_O_SIZE] = {
+    'a', 'e', 'i', 'u'
+};
+
+const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_U[EN_US_ADDITIONAL_U_SIZE] = {
+    'a', 'e', 'i', 'o'
+};
+}
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h
new file mode 100644
index 0000000..e0ecc0e
--- /dev/null
+++ b/native/jni/src/additional_proximity_chars.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
+#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
+
+#include <stdint.h>
+#include <string>
+
+#include "defines.h"
+
+namespace latinime {
+
+class AdditionalProximityChars {
+ private:
+    static const std::string LOCALE_EN_US;
+    static const int EN_US_ADDITIONAL_A_SIZE = 4;
+    static const int32_t EN_US_ADDITIONAL_A[];
+    static const int EN_US_ADDITIONAL_E_SIZE = 4;
+    static const int32_t EN_US_ADDITIONAL_E[];
+    static const int EN_US_ADDITIONAL_I_SIZE = 4;
+    static const int32_t EN_US_ADDITIONAL_I[];
+    static const int EN_US_ADDITIONAL_O_SIZE = 4;
+    static const int32_t EN_US_ADDITIONAL_O[];
+    static const int EN_US_ADDITIONAL_U_SIZE = 4;
+    static const int32_t EN_US_ADDITIONAL_U[];
+
+    static bool isEnLocale(const std::string *locale_str) {
+        return locale_str && locale_str->size() >= LOCALE_EN_US.size()
+                && LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str);
+    }
+
+ public:
+    static int getAdditionalCharsSize(const std::string* locale_str, const int32_t c) {
+        if (!isEnLocale(locale_str)) {
+            return 0;
+        }
+        switch(c) {
+        case 'a':
+            return EN_US_ADDITIONAL_A_SIZE;
+        case 'e':
+            return EN_US_ADDITIONAL_E_SIZE;
+        case 'i':
+            return EN_US_ADDITIONAL_I_SIZE;
+        case 'o':
+            return EN_US_ADDITIONAL_O_SIZE;
+        case 'u':
+            return EN_US_ADDITIONAL_U_SIZE;
+        default:
+            return 0;
+        }
+    }
+
+    static const int32_t* getAdditionalChars(const std::string *locale_str, const int32_t c) {
+        if (!isEnLocale(locale_str)) {
+            return 0;
+        }
+        switch(c) {
+        case 'a':
+            return EN_US_ADDITIONAL_A;
+        case 'e':
+            return EN_US_ADDITIONAL_E;
+        case 'i':
+            return EN_US_ADDITIONAL_I;
+        case 'o':
+            return EN_US_ADDITIONAL_O;
+        case 'u':
+            return EN_US_ADDITIONAL_U;
+        default:
+            return 0;
+        }
+    }
+
+    static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
+        return getAdditionalCharsSize(locale_str, c) > 0;
+    }
+};
+
+}
+
+#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
diff --git a/native/src/basechars.h b/native/jni/src/basechars.cpp
similarity index 97%
rename from native/src/basechars.h
rename to native/jni/src/basechars.cpp
index 3843e11..31f1e18 100644
--- a/native/src/basechars.h
+++ b/native/jni/src/basechars.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-#ifndef LATINIME_BASECHARS_H
-#define LATINIME_BASECHARS_H
+#include "char_utils.h"
+
+namespace latinime {
 
 /**
  * Table mapping most combined Latin, Greek, and Cyrillic characters
@@ -23,7 +24,7 @@
  * if c is not a combined character, or the base character if it
  * is combined.
  */
-static unsigned short BASE_CHARS[] = {
+const unsigned short BASE_CHARS[BASE_CHARS_SIZE] = {
     0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
     0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
     0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
@@ -189,4 +190,5 @@
 
 // generated with:
 // cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
-#endif // LATINIME_BASECHARS_H
+
+} // namespace latinime
diff --git a/native/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
similarity index 86%
rename from native/src/bigram_dictionary.cpp
rename to native/jni/src/bigram_dictionary.cpp
index c340c6c..f7a3d3e 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -26,14 +26,14 @@
 namespace latinime {
 
 BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength,
-        int maxAlternatives, const bool isLatestDictVersion, const bool hasBigram,
+        const bool isLatestDictVersion, const bool hasBigram,
         Dictionary *parentDictionary)
-    : DICT(dict + NEW_DICTIONARY_HEADER_SIZE), MAX_WORD_LENGTH(maxWordLength),
-    MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion),
+    : DICT(dict), MAX_WORD_LENGTH(maxWordLength),
+    IS_LATEST_DICT_VERSION(isLatestDictVersion),
     HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) {
     if (DEBUG_DICT) {
-        LOGI("BigramDictionary - constructor");
-        LOGI("Has Bigram : %d", hasBigram);
+        AKLOGI("BigramDictionary - constructor");
+        AKLOGI("Has Bigram : %d", hasBigram);
     }
 }
 
@@ -46,7 +46,7 @@
 #ifdef FLAG_DBG
         char s[length + 1];
         for (int i = 0; i <= length; i++) s[i] = word[i];
-        LOGI("Bigram: Found word = %s, freq = %d :", s, frequency);
+        AKLOGI("Bigram: Found word = %s, freq = %d :", s, frequency);
 #endif
     }
 
@@ -60,7 +60,7 @@
         insertAt++;
     }
     if (DEBUG_DICT) {
-        LOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+        AKLOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
     }
     if (insertAt < mMaxBigrams) {
         memmove((char*) mBigramFreq + (insertAt + 1) * sizeof(mBigramFreq[0]),
@@ -76,7 +76,7 @@
         }
         *dest = 0; // NULL terminate
         if (DEBUG_DICT) {
-            LOGI("Bigram: Added word at %d", insertAt);
+            AKLOGI("Bigram: Added word at %d", insertAt);
         }
         return true;
     }
@@ -92,7 +92,6 @@
  * bigramFreq: an array to output frequencies.
  * maxWordLength: the maximum size of a word.
  * maxBigrams: the maximum number of bigrams fitting in the bigramChars array.
- * maxAlteratives: unused.
  * This method returns the number of bigrams this word has, for backward compatibility.
  * Note: this is not the number of bigrams output in the array, which is the number of
  * bigrams this word has WHOSE first letter also matches the letter the user typed.
@@ -103,7 +102,7 @@
  */
 int BigramDictionary::getBigrams(unsigned short *prevWord, int prevWordLength, int *codes,
         int codesSize, unsigned short *bigramChars, int *bigramFreq, int maxWordLength,
-        int maxBigrams, int maxAlternatives) {
+        int maxBigrams) {
     // TODO: remove unused arguments, and refrain from storing stuff in members of this class
     // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
     mBigramFreq = bigramFreq;
@@ -134,11 +133,13 @@
         const int length = BinaryFormat::getWordAtAddress(root, bigramPos, MAX_WORD_LENGTH,
                 bigramBuffer);
 
-        if (checkFirstCharacter(bigramBuffer)) {
+        // codesSize == 0 means we are trying to find bigram predictions.
+        if (codesSize < 1 || checkFirstCharacter(bigramBuffer)) {
             const int frequency = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags;
-            addWordBigram(bigramBuffer, length, frequency);
+            if (addWordBigram(bigramBuffer, length, frequency)) {
+                ++bigramCount;
+            }
         }
-        ++bigramCount;
     } while (0 != (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags));
     return bigramCount;
 }
@@ -149,8 +150,9 @@
 
     int *inputCodes = mInputCodes;
     int maxAlt = MAX_ALTERNATIVES;
+    const unsigned short firstBaseChar = toBaseLowerCase(*word);
     while (maxAlt > 0) {
-        if ((unsigned int) *inputCodes == (unsigned int) *word) {
+        if (toBaseLowerCase(*inputCodes) == firstBaseChar) {
             return true;
         }
         inputCodes++;
diff --git a/native/src/bigram_dictionary.h b/native/jni/src/bigram_dictionary.h
similarity index 91%
rename from native/src/bigram_dictionary.h
rename to native/jni/src/bigram_dictionary.h
index c07458a..8132fbc 100644
--- a/native/src/bigram_dictionary.h
+++ b/native/jni/src/bigram_dictionary.h
@@ -21,14 +21,13 @@
 
 class Dictionary;
 class BigramDictionary {
-public:
-    BigramDictionary(const unsigned char *dict, int maxWordLength, int maxAlternatives,
+ public:
+    BigramDictionary(const unsigned char *dict, int maxWordLength,
             const bool isLatestDictVersion, const bool hasBigram, Dictionary *parentDictionary);
     int getBigrams(unsigned short *word, int length, int *codes, int codesSize,
-            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams,
-            int maxAlternatives);
+            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams);
     ~BigramDictionary();
-private:
+ private:
     bool addWordBigram(unsigned short *word, int length, int frequency);
     int getBigramAddress(int *pos, bool advance);
     int getBigramFreq(int *pos);
@@ -39,7 +38,8 @@
 
     const unsigned char *DICT;
     const int MAX_WORD_LENGTH;
-    const int MAX_ALTERNATIVES;
+    // TODO: Re-implement proximity correction for bigram correction
+    static const int MAX_ALTERNATIVES = 1;
     const bool IS_LATEST_DICT_VERSION;
     const bool HAS_BIGRAM;
 
diff --git a/native/src/binary_format.h b/native/jni/src/binary_format.h
similarity index 89%
rename from native/src/binary_format.h
rename to native/jni/src/binary_format.h
index 6f65088..ab033ad 100644
--- a/native/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -17,22 +17,31 @@
 #ifndef LATINIME_BINARY_FORMAT_H
 #define LATINIME_BINARY_FORMAT_H
 
+#include <limits>
 #include "unigram_dictionary.h"
 
 namespace latinime {
 
 class BinaryFormat {
-private:
+ private:
     const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
     const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
     const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
 
-public:
+ public:
     const static int UNKNOWN_FORMAT = -1;
-    const static int FORMAT_VERSION_1 = 1;
-    const static uint16_t FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B1;
+    // Originally, format version 1 had a 16-bit magic number, then the version number `01'
+    // then options that must be 0. Hence the first 32-bits of the format are always as follow
+    // and it's okay to consider them a magic number as a whole.
+    const static uint32_t FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100;
+    const static unsigned int FORMAT_VERSION_1_HEADER_SIZE = 5;
+    // The versions of Latin IME that only handle format version 1 only test for the magic
+    // number, so we had to change it so that version 2 files would be rejected by older
+    // implementations. On this occasion, we made the magic number 32 bits long.
+    const static uint32_t FORMAT_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
 
     static int detectFormat(const uint8_t* const dict);
+    static unsigned int getHeaderSize(const uint8_t* const dict);
     static int getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos);
     static uint8_t getFlagsAndForwardPointer(const uint8_t* const dict, int* pos);
     static int32_t getCharCodeAndForwardPointer(const uint8_t* const dict, int* pos);
@@ -55,13 +64,43 @@
 };
 
 inline int BinaryFormat::detectFormat(const uint8_t* const dict) {
-    const uint16_t magicNumber = (dict[0] << 8) + dict[1]; // big endian
-    if (FORMAT_VERSION_1_MAGIC_NUMBER == magicNumber) return FORMAT_VERSION_1;
-    return UNKNOWN_FORMAT;
+    // The magic number is stored big-endian.
+    const uint32_t magicNumber = (dict[0] << 24) + (dict[1] << 16) + (dict[2] << 8) + dict[3];
+    switch (magicNumber) {
+    case FORMAT_VERSION_1_MAGIC_NUMBER:
+        // Format 1 header is exactly 5 bytes long and looks like:
+        // Magic number (2 bytes) 0x78 0xB1
+        // Version number (1 byte) 0x01
+        // Options (2 bytes) must be 0x00 0x00
+        return 1;
+    case FORMAT_VERSION_2_MAGIC_NUMBER:
+        // Format 2 header is as follows:
+        // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
+        // Version number (2 bytes) 0x00 0x02
+        // Options (2 bytes) must be 0x00 0x00
+        // Header size (4 bytes) : integer, big endian
+        return (dict[4] << 8) + dict[5];
+    default:
+        return UNKNOWN_FORMAT;
+    }
+}
+
+inline unsigned int BinaryFormat::getHeaderSize(const uint8_t* const dict) {
+    switch (detectFormat(dict)) {
+    case 1:
+        return FORMAT_VERSION_1_HEADER_SIZE;
+    case 2:
+        // See the format of the header in the comment in detectFormat() above
+        return (dict[8] << 24) + (dict[9] << 16) + (dict[10] << 8) + dict[11];
+    default:
+        return std::numeric_limits<unsigned int>::max();
+    }
 }
 
 inline int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos) {
-    return dict[(*pos)++];
+    const int msb = dict[(*pos)++];
+    if (msb < 0x80) return msb;
+    return ((msb & 0x7F) << 8) | dict[(*pos)++];
 }
 
 inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t* const dict, int* pos) {
@@ -145,15 +184,15 @@
 
 inline int BinaryFormat::skipAllAttributes(const uint8_t* const dict, const uint8_t flags,
         const int pos) {
-    // This function skips all attributes. The format makes provision for future extension
-    // with other attributes (notably shortcuts) but for the time being, bigrams are the
-    // only attributes that may be found in a character group, so we only look at bigrams
-    // in this version.
-    if (UnigramDictionary::FLAG_HAS_BIGRAMS & flags) {
-        return skipAttributes(dict, pos);
-    } else {
-        return pos;
+    // This function skips all attributes: shortcuts and bigrams.
+    int newPos = pos;
+    if (UnigramDictionary::FLAG_HAS_SHORTCUT_TARGETS & flags) {
+        newPos = skipAttributes(dict, newPos);
     }
+    if (UnigramDictionary::FLAG_HAS_BIGRAMS & flags) {
+        newPos = skipAttributes(dict, newPos);
+    }
+    return newPos;
 }
 
 inline int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t* const dict,
diff --git a/native/src/char_utils.cpp b/native/jni/src/char_utils.cpp
similarity index 100%
rename from native/src/char_utils.cpp
rename to native/jni/src/char_utils.cpp
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
new file mode 100644
index 0000000..607dc51
--- /dev/null
+++ b/native/jni/src/char_utils.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_CHAR_UTILS_H
+#define LATINIME_CHAR_UTILS_H
+
+namespace latinime {
+
+inline static int isAsciiUpper(unsigned short c) {
+    return c >= 'A' && c <= 'Z';
+}
+
+inline static unsigned short toAsciiLower(unsigned short c) {
+    return c - 'A' + 'a';
+}
+
+inline static int isAscii(unsigned short c) {
+    return c <= 127;
+}
+
+unsigned short latin_tolower(unsigned short c);
+
+/**
+ * Table mapping most combined Latin, Greek, and Cyrillic characters
+ * to their base characters.  If c is in range, BASE_CHARS[c] == c
+ * if c is not a combined character, or the base character if it
+ * is combined.
+ */
+
+static const int BASE_CHARS_SIZE = 0x0500;
+extern const unsigned short BASE_CHARS[BASE_CHARS_SIZE];
+
+inline static unsigned short toBaseChar(unsigned short c) {
+    if (c < BASE_CHARS_SIZE) {
+        return BASE_CHARS[c];
+    }
+    return c;
+}
+
+inline static unsigned short toBaseLowerCase(unsigned short c) {
+    c = toBaseChar(c);
+    if (isAsciiUpper(c)) {
+        return toAsciiLower(c);
+    } else if (isAscii(c)) {
+        return c;
+    }
+    return latin_tolower(c);
+}
+
+} // namespace latinime
+
+#endif // LATINIME_CHAR_UTILS_H
diff --git a/native/src/correction.cpp b/native/jni/src/correction.cpp
similarity index 60%
rename from native/src/correction.cpp
rename to native/jni/src/correction.cpp
index 27dc407..087219e 100644
--- a/native/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -16,12 +16,15 @@
 
 #include <assert.h>
 #include <ctype.h>
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 
 #define LOG_TAG "LatinIME: correction.cpp"
 
+#include "char_utils.h"
 #include "correction.h"
+#include "defines.h"
 #include "dictionary.h"
 #include "proximity_info.h"
 
@@ -31,81 +34,60 @@
 // edit distance funcitons //
 /////////////////////////////
 
-#if 0 /* no longer used */
-inline static int editDistance(
-        int* editDistanceTable, const unsigned short* input,
-        const int inputLength, const unsigned short* output, const int outputLength) {
-    // dp[li][lo] dp[a][b] = dp[ a * lo + b]
-    int* dp = editDistanceTable;
-    const int li = inputLength + 1;
-    const int lo = outputLength + 1;
-    for (int i = 0; i < li; ++i) {
-        dp[lo * i] = i;
-    }
-    for (int i = 0; i < lo; ++i) {
-        dp[i] = i;
-    }
-
-    for (int i = 0; i < li - 1; ++i) {
-        for (int j = 0; j < lo - 1; ++j) {
-            const uint32_t ci = Dictionary::toBaseLowerCase(input[i]);
-            const uint32_t co = Dictionary::toBaseLowerCase(output[j]);
-            const uint16_t cost = (ci == co) ? 0 : 1;
-            dp[(i + 1) * lo + (j + 1)] = min(dp[i * lo + (j + 1)] + 1,
-                    min(dp[(i + 1) * lo + j] + 1, dp[i * lo + j] + cost));
-            if (i > 0 && j > 0 && ci == Dictionary::toBaseLowerCase(output[j - 1])
-                    && co == Dictionary::toBaseLowerCase(input[i - 1])) {
-                dp[(i + 1) * lo + (j + 1)] = min(
-                        dp[(i + 1) * lo + (j + 1)], dp[(i - 1) * lo + (j - 1)] + cost);
-            }
-        }
-    }
-
-    if (DEBUG_EDIT_DISTANCE) {
-        LOGI("IN = %d, OUT = %d", inputLength, outputLength);
-        for (int i = 0; i < li; ++i) {
-            for (int j = 0; j < lo; ++j) {
-                LOGI("EDIT[%d][%d], %d", i, j, dp[i * lo + j]);
-            }
-        }
-    }
-    return dp[li * lo - 1];
-}
-#endif
-
 inline static void initEditDistance(int *editDistanceTable) {
     for (int i = 0; i <= MAX_WORD_LENGTH_INTERNAL; ++i) {
         editDistanceTable[i] = i;
     }
 }
 
+inline static void dumpEditDistance10ForDebug(int *editDistanceTable,
+        const int editDistanceTableWidth, const int outputLength) {
+    if (DEBUG_DICT) {
+        AKLOGI("EditDistanceTable");
+        for (int i = 0; i <= 10; ++i) {
+            int c[11];
+            for (int j = 0; j <= 10; ++j) {
+                if (j < editDistanceTableWidth + 1 && i < outputLength + 1) {
+                    c[j] = (editDistanceTable + i * (editDistanceTableWidth + 1))[j];
+                } else {
+                    c[j] = -1;
+                }
+            }
+            AKLOGI("[ %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d ]",
+                    c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10]);
+        }
+    }
+}
+
 inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigned short *input,
         const int inputLength, const unsigned short *output, const int outputLength) {
+    // TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched.
     // Let dp[i][j] be editDistanceTable[i * (inputLength + 1) + j].
     // Assuming that dp[0][0] ... dp[outputLength - 1][inputLength] are already calculated,
     // and calculate dp[ouputLength][0] ... dp[outputLength][inputLength].
     int *const current = editDistanceTable + outputLength * (inputLength + 1);
     const int *const prev = editDistanceTable + (outputLength - 1) * (inputLength + 1);
     const int *const prevprev =
-            outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputLength + 1) : NULL;
+            outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputLength + 1) : 0;
     current[0] = outputLength;
-    const uint32_t co = Dictionary::toBaseLowerCase(output[outputLength - 1]);
-    const uint32_t prevCO =
-            outputLength >= 2 ? Dictionary::toBaseLowerCase(output[outputLength - 2]) : 0;
+    const uint32_t co = toBaseLowerCase(output[outputLength - 1]);
+    const uint32_t prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0;
     for (int i = 1; i <= inputLength; ++i) {
-        const uint32_t ci = Dictionary::toBaseLowerCase(input[i - 1]);
+        const uint32_t ci = toBaseLowerCase(input[i - 1]);
         const uint16_t cost = (ci == co) ? 0 : 1;
         current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost));
-        if (i >= 2 && prevprev && ci == prevCO
-                && co == Dictionary::toBaseLowerCase(input[i - 2])) {
+        if (i >= 2 && prevprev && ci == prevCO && co == toBaseLowerCase(input[i - 2])) {
             current[i] = min(current[i], prevprev[i - 2] + 1);
         }
     }
 }
 
-inline static int getCurrentEditDistance(
-        int *editDistanceTable, const int inputLength, const int outputLength) {
-    return editDistanceTable[(inputLength + 1) * (outputLength + 1) - 1];
+inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth,
+        const int outputLength, const int inputLength) {
+    if (DEBUG_EDIT_DISTANCE) {
+        AKLOGI("getCurrentEditDistance %d, %d", inputLength, outputLength);
+    }
+    return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputLength];
 }
 
 //////////////////////
@@ -133,6 +115,9 @@
     mInputLength = inputLength;
     mMaxDepth = maxDepth;
     mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2;
+    // TODO: This is not supposed to be required.  Check what's going wrong with
+    // editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL]
+    initEditDistance(mEditDistanceTable);
 }
 
 void Correction::initCorrectionState(
@@ -146,7 +131,7 @@
 
 void Correction::setCorrectionParams(const int skipPos, const int excessivePos,
         const int transposedPos, const int spaceProximityPos, const int missingSpacePos,
-        const bool useFullEditDistance) {
+        const bool useFullEditDistance, const bool doAutoCompletion, const int maxErrors) {
     // TODO: remove
     mTransposedPos = transposedPos;
     mExcessivePos = excessivePos;
@@ -159,6 +144,8 @@
     mSpaceProximityPos = spaceProximityPos;
     mMissingSpacePos = missingSpacePos;
     mUseFullEditDistance = useFullEditDistance;
+    mDoAutoCompletion = doAutoCompletion;
+    mMaxErrors = maxErrors;
 }
 
 void Correction::checkState() {
@@ -172,23 +159,34 @@
     }
 }
 
-int Correction::getFreqForSplitTwoWords(const int firstFreq, const int secondFreq,
-        const unsigned short *word) {
-    return Correction::RankingAlgorithm::calcFreqForSplitTwoWords(
-            firstFreq, secondFreq, this, word);
+int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
+        const int wordCount, const bool isSpaceProximity, const unsigned short *word) {
+    return Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(freqArray, wordLengthArray,
+            wordCount, this, isSpaceProximity, word);
 }
 
 int Correction::getFinalFreq(const int freq, unsigned short **word, int *wordLength) {
+    return getFinalFreqInternal(freq, word, wordLength, mInputLength);
+}
+
+int Correction::getFinalFreqForSubQueue(const int freq, unsigned short **word, int *wordLength,
+        const int inputLength) {
+    return getFinalFreqInternal(freq, word, wordLength, inputLength);
+}
+
+int Correction::getFinalFreqInternal(const int freq, unsigned short **word, int *wordLength,
+        const int inputLength) {
     const int outputIndex = mTerminalOutputIndex;
     const int inputIndex = mTerminalInputIndex;
     *wordLength = outputIndex + 1;
-    if (mProximityInfo->sameAsTyped(mWord, outputIndex + 1) || outputIndex < MIN_SUGGEST_DEPTH) {
-        return -1;
+    if (outputIndex < MIN_SUGGEST_DEPTH) {
+        return NOT_A_FREQUENCY;
     }
 
     *word = mWord;
-    return Correction::RankingAlgorithm::calculateFinalFreq(
-            inputIndex, outputIndex, freq, mEditDistanceTable, this);
+    int finalFreq = Correction::RankingAlgorithm::calculateFinalFreq(
+            inputIndex, outputIndex, freq, mEditDistanceTable, this, inputLength);
+    return finalFreq;
 }
 
 bool Correction::initProcessState(const int outputIndex) {
@@ -213,6 +211,7 @@
 
     mMatching = false;
     mProximityMatching = false;
+    mAdditionalProximityMatching = false;
     mTransposing = false;
     mExceeding = false;
     mSkipping = false;
@@ -229,20 +228,10 @@
 }
 
 // TODO: remove
-int Correction::getOutputIndex() {
-    return mOutputIndex;
-}
-
-// TODO: remove
 int Correction::getInputIndex() {
     return mInputIndex;
 }
 
-// TODO: remove
-bool Correction::needsToTraverseAllNodes() {
-    return mNeedsToTraverseAllNodes;
-}
-
 void Correction::incrementInputIndex() {
     ++mInputIndex;
 }
@@ -269,6 +258,7 @@
 
     mCorrectionStates[mOutputIndex].mMatching = mMatching;
     mCorrectionStates[mOutputIndex].mProximityMatching = mProximityMatching;
+    mCorrectionStates[mOutputIndex].mAdditionalProximityMatching = mAdditionalProximityMatching;
     mCorrectionStates[mOutputIndex].mTransposing = mTransposing;
     mCorrectionStates[mOutputIndex].mExceeding = mExceeding;
     mCorrectionStates[mOutputIndex].mSkipping = mSkipping;
@@ -280,7 +270,9 @@
 
 bool Correction::needsToPrune() const {
     // TODO: use edit distance here
-    return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance;
+    return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance
+            // Allow one char longer word for missing character
+            || (!mDoAutoCompletion && (mOutputIndex > mInputLength));
 }
 
 void Correction::addCharToCurrentWord(const int32_t c) {
@@ -290,13 +282,12 @@
             mWord, mOutputIndex + 1);
 }
 
-// TODO: inline?
 Correction::CorrectionType Correction::processSkipChar(
         const int32_t c, const bool isTerminal, const bool inputIndexIncremented) {
     addCharToCurrentWord(c);
-    if (needsToTraverseAllNodes() && isTerminal) {
-        mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0);
-        mTerminalOutputIndex = mOutputIndex;
+    mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0);
+    mTerminalOutputIndex = mOutputIndex;
+    if (mNeedsToTraverseAllNodes && isTerminal) {
         incrementOutputIndex();
         return TRAVERSE_ALL_ON_TERMINAL;
     } else {
@@ -305,19 +296,36 @@
     }
 }
 
+Correction::CorrectionType Correction::processUnrelatedCorrectionType() {
+    // Needs to set mTerminalInputIndex and mTerminalOutputIndex before returning any CorrectionType
+    mTerminalInputIndex = mInputIndex;
+    mTerminalOutputIndex = mOutputIndex;
+    return UNRELATED;
+}
+
 inline bool isEquivalentChar(ProximityInfo::ProximityType type) {
     return type == ProximityInfo::EQUIVALENT_CHAR;
 }
 
+inline bool isProximityCharOrEquivalentChar(ProximityInfo::ProximityType type) {
+    return type == ProximityInfo::EQUIVALENT_CHAR
+            || type == ProximityInfo::NEAR_PROXIMITY_CHAR;
+}
+
 Correction::CorrectionType Correction::processCharAndCalcState(
         const int32_t c, const bool isTerminal) {
     const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount);
+    if (correctionCount > mMaxErrors) {
+        return processUnrelatedCorrectionType();
+    }
+
     // TODO: Change the limit if we'll allow two or more corrections
     const bool noCorrectionsHappenedSoFar = correctionCount == 0;
     const bool canTryCorrection = noCorrectionsHappenedSoFar;
     int proximityIndex = 0;
     mDistances[mOutputIndex] = NOT_A_DISTANCE;
 
+    // Skip checking this node
     if (mNeedsToTraverseAllNodes || isQuote(c)) {
         bool incremented = false;
         if (mLastCharExceeded && mInputIndex == mInputLength - 1) {
@@ -342,6 +350,7 @@
         return processSkipChar(c, isTerminal, incremented);
     }
 
+    // Check possible corrections.
     if (mExcessivePos >= 0) {
         if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) {
             mExcessivePos = mOutputIndex;
@@ -382,30 +391,42 @@
             incrementInputIndex();
         } else {
             --mTransposedCount;
-            if (DEBUG_CORRECTION) {
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 DUMP_WORD(mWord, mOutputIndex);
-                LOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                         mTransposedCount, mExcessiveCount, c);
             }
-            return UNRELATED;
+            return processUnrelatedCorrectionType();
         }
     }
 
     // TODO: Change the limit if we'll allow two or more proximity chars with corrections
-    const bool checkProximityChars = noCorrectionsHappenedSoFar ||  mProximityCount == 0;
+    // Work around: When the mMaxErrors is 1, we only allow just one error
+    // including proximity correction.
+    const bool checkProximityChars = (mMaxErrors > 1)
+            ? (noCorrectionsHappenedSoFar || mProximityCount == 0)
+            : (noCorrectionsHappenedSoFar && mProximityCount == 0);
+
     ProximityInfo::ProximityType matchedProximityCharId = secondTransposing
             ? ProximityInfo::EQUIVALENT_CHAR
             : mProximityInfo->getMatchedProximityId(
                     mInputIndex, c, checkProximityChars, &proximityIndex);
 
-    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
+    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
+            || ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
         if (canTryCorrection && mOutputIndex > 0
                 && mCorrectionStates[mOutputIndex].mProximityMatching
                 && mCorrectionStates[mOutputIndex].mExceeding
                 && isEquivalentChar(mProximityInfo->getMatchedProximityId(
                         mInputIndex, mWord[mOutputIndex - 1], false))) {
-            if (DEBUG_CORRECTION) {
-                LOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
             }
             // Conversion p->e
             // Example:
@@ -423,7 +444,11 @@
         }
     }
 
-    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
+    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
+            || ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+        if (ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+            mAdditionalProximityMatching = true;
+        }
         // TODO: Optimize
         // As the current char turned out to be an unrelated char,
         // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
@@ -465,6 +490,18 @@
             ++mSkippedCount;
             --mProximityCount;
             return processSkipChar(c, isTerminal, false);
+        } else if (mInputIndex - 1 < mInputLength
+                && mSkippedCount > 0
+                && mCorrectionStates[mOutputIndex].mSkipping
+                && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
+                && isProximityCharOrEquivalentChar(
+                        mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) {
+            // Conversion s->a
+            incrementInputIndex();
+            --mSkippedCount;
+            mProximityMatching = true;
+            ++mProximityCount;
+            mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
         } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength
                 && isEquivalentChar(
                         mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) {
@@ -475,17 +512,52 @@
                 ++mExcessiveCount;
                 incrementInputIndex();
             }
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                DUMP_WORD(mWord, mOutputIndex);
+                if (mTransposing) {
+                    AKLOGI("TRANSPOSE: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                            mTransposedCount, mExcessiveCount, c);
+                } else {
+                    AKLOGI("EXCEED: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                            mTransposedCount, mExcessiveCount, c);
+                }
+            }
         } else if (mSkipping) {
             // 3. Skip correction
             ++mSkippedCount;
-            return processSkipChar(c, isTerminal, false);
-        } else {
-            if (DEBUG_CORRECTION) {
-                DUMP_WORD(mWord, mOutputIndex);
-                LOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                         mTransposedCount, mExcessiveCount, c);
             }
-            return UNRELATED;
+            return processSkipChar(c, isTerminal, false);
+        } else if (ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+            // As a last resort, use additional proximity characters
+            mProximityMatching = true;
+            ++mProximityCount;
+            mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                        mTransposedCount, mExcessiveCount, c);
+            }
+        } else {
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                DUMP_WORD(mWord, mOutputIndex);
+                AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                        mTransposedCount, mExcessiveCount, c);
+            }
+            return processUnrelatedCorrectionType();
         }
     } else if (secondTransposing) {
         // If inputIndex is greater than mInputLength, that means there is no
@@ -500,6 +572,13 @@
         ++mProximityCount;
         mDistances[mOutputIndex] =
                 mProximityInfo->getNormalizedSquaredDistance(mInputIndex, proximityIndex);
+        if (DEBUG_CORRECTION
+                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                        || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+            AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                    mTransposedCount, mExcessiveCount, c);
+        }
     }
 
     addCharToCurrentWord(c);
@@ -533,13 +612,17 @@
             || isSameAsUserTypedLength) && isTerminal) {
         mTerminalInputIndex = mInputIndex - 1;
         mTerminalOutputIndex = mOutputIndex - 1;
-        if (DEBUG_CORRECTION) {
+        if (DEBUG_CORRECTION
+                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
             DUMP_WORD(mWord, mOutputIndex);
-            LOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+            AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                     mTransposedCount, mExcessiveCount, c);
         }
         return ON_TERMINAL;
     } else {
+        mTerminalInputIndex = mInputIndex - 1;
+        mTerminalOutputIndex = mOutputIndex - 1;
         return NOT_ON_TERMINAL;
     }
 }
@@ -547,55 +630,6 @@
 Correction::~Correction() {
 }
 
-/////////////////////////
-// static inline utils //
-/////////////////////////
-
-static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
-static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
-    return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
-}
-
-static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
-inline static void multiplyIntCapped(const int multiplier, int *base) {
-    const int temp = *base;
-    if (temp != S_INT_MAX) {
-        // Branch if multiplier == 2 for the optimization
-        if (multiplier == 2) {
-            *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
-        } else {
-            // TODO: This overflow check gives a wrong answer when, for example,
-            //       temp = 2^16 + 1 and multiplier = 2^17 + 1.
-            //       Fix this behavior.
-            const int tempRetval = temp * multiplier;
-            *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
-        }
-    }
-}
-
-inline static int powerIntCapped(const int base, const int n) {
-    if (n <= 0) return 1;
-    if (base == 2) {
-        return n < 31 ? 1 << n : S_INT_MAX;
-    } else {
-        int ret = base;
-        for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret);
-        return ret;
-    }
-}
-
-inline static void multiplyRate(const int rate, int *freq) {
-    if (*freq != S_INT_MAX) {
-        if (*freq > 1000000) {
-            *freq /= 100;
-            multiplyIntCapped(rate, freq);
-        } else {
-            multiplyIntCapped(rate, freq);
-            *freq /= 100;
-        }
-    }
-}
-
 inline static int getQuoteCount(const unsigned short* word, const int length) {
     int quoteCount = 0;
     for (int i = 0; i < length; ++i) {
@@ -607,13 +641,7 @@
 }
 
 inline static bool isUpperCase(unsigned short c) {
-     if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) {
-         c = BASE_CHARS[c];
-     }
-     if (isupper(c)) {
-         return true;
-     }
-     return false;
+    return isAsciiUpper(toBaseChar(c));
 }
 
 //////////////////////
@@ -622,9 +650,9 @@
 
 /* static */
 int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const int outputIndex,
-        const int freq, int* editDistanceTable, const Correction* correction) {
+        const int freq, int* editDistanceTable, const Correction* correction,
+        const int inputLength) {
     const int excessivePos = correction->getExcessivePos();
-    const int inputLength = correction->mInputLength;
     const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
     const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
     const ProximityInfo *proximityInfo = correction->mProximityInfo;
@@ -649,45 +677,55 @@
     const unsigned short* word = correction->mWord;
     const bool skipped = skippedCount > 0;
 
-    const int quoteDiffCount = max(0, getQuoteCount(word, outputIndex + 1)
+    const int quoteDiffCount = max(0, getQuoteCount(word, outputLength)
             - getQuoteCount(proximityInfo->getPrimaryInputWord(), inputLength));
 
     // TODO: Calculate edit distance for transposed and excessive
     int ed = 0;
+    if (DEBUG_DICT_FULL) {
+        dumpEditDistance10ForDebug(editDistanceTable, correction->mInputLength, outputLength);
+    }
     int adjustedProximityMatchedCount = proximityMatchedCount;
 
     int finalFreq = freq;
 
+    if (DEBUG_CORRECTION_FREQ
+            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
+        AKLOGI("FinalFreq0: %d", finalFreq);
+    }
     // TODO: Optimize this.
-    // TODO: Ignoring edit distance for transposed char, for now
-    if (transposedCount == 0 && (proximityMatchedCount > 0 || skipped || excessiveCount > 0)) {
-        ed = getCurrentEditDistance(editDistanceTable, inputLength, outputIndex + 1);
+    if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) {
+        ed = getCurrentEditDistance(editDistanceTable, correction->mInputLength, outputLength,
+                inputLength) - transposedCount;
+
         const int matchWeight = powerIntCapped(typedLetterMultiplier,
-                max(inputLength, outputIndex + 1) - ed);
+                max(inputLength, outputLength) - ed);
         multiplyIntCapped(matchWeight, &finalFreq);
 
         // TODO: Demote further if there are two or more excessive chars with longer user input?
-        if (inputLength > outputIndex + 1) {
+        if (inputLength > outputLength) {
             multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq);
         }
 
         ed = max(0, ed - quoteDiffCount);
-
-        if (ed == 1 && (inputLength == outputIndex || inputLength == outputIndex + 2)) {
-            // Promote a word with just one skipped or excessive char
-            if (sameLength) {
-                multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE, &finalFreq);
-            } else {
-                multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-            }
-        } else if (ed == 0) {
-            multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-            sameLength = true;
-        }
-        adjustedProximityMatchedCount = min(max(0, ed - (outputIndex + 1 - inputLength)),
+        adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputLength)),
                 proximityMatchedCount);
+        if (transposedCount < 1) {
+            if (ed == 1 && (inputLength == outputLength - 1 || inputLength == outputLength + 1)) {
+                // Promote a word with just one skipped or excessive char
+                if (sameLength) {
+                    multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE
+                            + WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER * outputLength,
+                            &finalFreq);
+                } else {
+                    multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+                }
+            } else if (ed == 0) {
+                multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+                sameLength = true;
+            }
+        }
     } else {
-        // TODO: Calculate the edit distance for transposed char
         const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
         multiplyIntCapped(matchWeight, &finalFreq);
     }
@@ -707,7 +745,7 @@
                 / (10 * inputLength
                         - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
         if (DEBUG_DICT_FULL) {
-            LOGI("Demotion rate for missing character is %d.", demotionRate);
+            AKLOGI("Demotion rate for missing character is %d.", demotionRate);
         }
         multiplyRate(demotionRate, &finalFreq);
     }
@@ -720,8 +758,8 @@
     if (excessiveCount > 0) {
         multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
         if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) {
-            if (DEBUG_CORRECTION_FREQ) {
-                LOGI("Double excessive demotion");
+            if (DEBUG_DICT_FULL) {
+                AKLOGI("Double excessive demotion");
             }
             // If an excessive character is not adjacent to the left char or the right char,
             // we will demote this word.
@@ -729,11 +767,14 @@
         }
     }
 
+    const bool performTouchPositionCorrection =
+            CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
+                        && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0;
     // Score calibration by touch coordinates is being done only for pure-fat finger typing error
     // cases.
+    int additionalProximityCount = 0;
     // TODO: Remove this constraint.
-    if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
-            && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0) {
+    if (performTouchPositionCorrection) {
         for (int i = 0; i < outputLength; ++i) {
             const int squaredDistance = correction->mDistances[i];
             if (i < adjustedProximityMatchedCount) {
@@ -744,40 +785,60 @@
                 static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f;
                 static const float B = 1.0f;
                 static const float C = 0.5f;
+                static const float MIN = 0.3f;
                 static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
                 static const float R2 = HALF_SCORE_SQUARED_RADIUS;
                 const float x = (float)squaredDistance
                         / ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
-                const float factor = (x < R1)
+                const float factor = max((x < R1)
                     ? (A * (R1 - x) + B * x) / R1
-                    : (B * (R2 - x) + C * (x - R1)) / (R2 - R1);
+                    : (B * (R2 - x) + C * (x - R1)) / (R2 - R1), MIN);
                 // factor is piecewise linear function like:
                 // A -_                  .
                 //     ^-_               .
                 // B      \              .
-                //         \             .
-                // C        \            .
-                //   0   R1 R2
-                if (factor <= 0) {
-                    return -1;
-                }
+                //         \_            .
+                // C         ------------.
+                //                       .
+                // 0   R1 R2             .
                 multiplyRate((int)(factor * 100), &finalFreq);
             } else if (squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
                 multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+            } else if (squaredDistance == ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO) {
+                ++additionalProximityCount;
+                multiplyRate(WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
             }
         }
     } else {
+        // Demote additional proximity characters
+        for (int i = 0; i < outputLength; ++i) {
+            const int squaredDistance = correction->mDistances[i];
+            if (squaredDistance == ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO) {
+                ++additionalProximityCount;
+            }
+        }
         // Promotion for a word with proximity characters
         for (int i = 0; i < adjustedProximityMatchedCount; ++i) {
             // A word with proximity corrections
             if (DEBUG_DICT_FULL) {
-                LOGI("Found a proximity correction.");
+                AKLOGI("Found a proximity correction.");
             }
             multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-            multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+            if (i < additionalProximityCount) {
+                multiplyRate(WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+            } else {
+                multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+            }
         }
     }
 
+    // If the user types too many(three or more) proximity characters with additional proximity
+    // character,do not treat as the same length word.
+    if (sameLength && additionalProximityCount > 0 && (adjustedProximityMatchedCount >= 3
+            || transposedCount > 0 || skipped || excessiveCount > 0)) {
+        sameLength = false;
+    }
+
     const int errorCount = adjustedProximityMatchedCount > 0
             ? adjustedProximityMatchedCount
             : (proximityMatchedCount + transposedCount);
@@ -787,13 +848,15 @@
     // Promotion for an exactly matched word
     if (ed == 0) {
         // Full exact match
-        if (sameLength && transposedCount == 0 && !skipped && excessiveCount == 0) {
+        if (sameLength && transposedCount == 0 && !skipped && excessiveCount == 0
+                && quoteDiffCount == 0 && additionalProximityCount == 0) {
             finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
         }
     }
 
     // Promote a word with no correction
-    if (proximityMatchedCount == 0 && transposedCount == 0 && !skipped && excessiveCount == 0) {
+    if (proximityMatchedCount == 0 && transposedCount == 0 && !skipped && excessiveCount == 0
+            && additionalProximityCount == 0) {
         multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
     }
 
@@ -832,71 +895,101 @@
     }
 
     if (DEBUG_DICT_FULL) {
-        LOGI("calc: %d, %d", outputIndex, sameLength);
+        AKLOGI("calc: %d, %d", outputLength, sameLength);
     }
 
-    if (DEBUG_CORRECTION_FREQ) {
-        DUMP_WORD(correction->mWord, outputIndex + 1);
-        LOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d", proximityMatchedCount,
-                skippedCount, transposedCount, excessiveCount, lastCharExceeded, sameLength,
-                quoteDiffCount, ed, finalFreq);
+    if (DEBUG_CORRECTION_FREQ
+            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
+        DUMP_WORD(proximityInfo->getPrimaryInputWord(), inputLength);
+        DUMP_WORD(correction->mWord, outputLength);
+        AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
+                skippedCount, transposedCount, excessiveCount, additionalProximityCount,
+                outputLength, lastCharExceeded, sameLength, quoteDiffCount, ed, finalFreq);
     }
 
     return finalFreq;
 }
 
 /* static */
-int Correction::RankingAlgorithm::calcFreqForSplitTwoWords(
-        const int firstFreq, const int secondFreq, const Correction* correction,
-        const unsigned short *word) {
-    const int spaceProximityPos = correction->mSpaceProximityPos;
-    const int missingSpacePos = correction->mMissingSpacePos;
-    if (DEBUG_DICT) {
-        int inputCount = 0;
-        if (spaceProximityPos >= 0) ++inputCount;
-        if (missingSpacePos >= 0) ++inputCount;
-        assert(inputCount <= 1);
-    }
-    const bool isSpaceProximity = spaceProximityPos >= 0;
-    const int inputLength = correction->mInputLength;
-    const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
-    const int secondWordLength = isSpaceProximity ? (inputLength - spaceProximityPos - 1)
-            : (inputLength - missingSpacePos);
+int Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(
+        const int *freqArray, const int *wordLengthArray, const int wordCount,
+        const Correction* correction, const bool isSpaceProximity, const unsigned short *word) {
     const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
 
     bool firstCapitalizedWordDemotion = false;
-    if (firstWordLength >= 2) {
-        firstCapitalizedWordDemotion = isUpperCase(word[0]);
+    bool secondCapitalizedWordDemotion = false;
+
+    {
+        // TODO: Handle multiple capitalized word demotion properly
+        const int firstWordLength = wordLengthArray[0];
+        const int secondWordLength = wordLengthArray[1];
+        if (firstWordLength >= 2) {
+            firstCapitalizedWordDemotion = isUpperCase(word[0]);
+        }
+
+        if (secondWordLength >= 2) {
+            // FIXME: word[firstWordLength + 1] is incorrect.
+            secondCapitalizedWordDemotion = isUpperCase(word[firstWordLength + 1]);
+        }
     }
 
-    bool secondCapitalizedWordDemotion = false;
-    if (secondWordLength >= 2) {
-        secondCapitalizedWordDemotion = isUpperCase(word[firstWordLength + 1]);
-    }
 
     const bool capitalizedWordDemotion =
             firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion;
 
-    if (DEBUG_DICT_FULL) {
-        LOGI("Two words: %c, %c, %d", word[0], word[firstWordLength + 1], capitalizedWordDemotion);
+    int totalLength = 0;
+    int totalFreq = 0;
+    for (int i = 0; i < wordCount; ++i){
+        const int wordLength = wordLengthArray[i];
+        if (wordLength <= 0) {
+            return 0;
+        }
+        totalLength += wordLength;
+        const int demotionRate = 100 - TWO_WORDS_CORRECTION_DEMOTION_BASE / (wordLength + 1);
+        int tempFirstFreq = freqArray[i];
+        multiplyRate(demotionRate, &tempFirstFreq);
+        totalFreq += tempFirstFreq;
     }
 
-    if (firstWordLength == 0 || secondWordLength == 0) {
+    if (totalLength <= 0 || totalFreq <= 0) {
         return 0;
     }
-    const int firstDemotionRate = 100 - 100 / (firstWordLength + 1);
-    int tempFirstFreq = firstFreq;
-    multiplyRate(firstDemotionRate, &tempFirstFreq);
 
-    const int secondDemotionRate = 100 - 100 / (secondWordLength + 1);
-    int tempSecondFreq = secondFreq;
-    multiplyRate(secondDemotionRate, &tempSecondFreq);
-
-    const int totalLength = firstWordLength + secondWordLength;
-
+    // TODO: Currently totalFreq is adjusted to two word metrix.
     // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
     // length.
-    int totalFreq = tempFirstFreq + tempSecondFreq;
+    totalFreq = totalFreq * 2 / wordCount;
+    if (wordCount > 2) {
+        // Safety net for 3+ words -- Caveats: many heuristics and workarounds here.
+        int oneLengthCounter = 0;
+        int twoLengthCounter = 0;
+        for (int i = 0; i < wordCount; ++i) {
+            const int wordLength = wordLengthArray[i];
+            // TODO: Use bigram instead of this safety net
+            if (i < wordCount - 1) {
+                const int nextWordLength = wordLengthArray[i + 1];
+                if (wordLength == 1 && nextWordLength == 2) {
+                    // Safety net to filter 1 length and 2 length sequential words
+                    return 0;
+                }
+            }
+            const int freq = freqArray[i];
+            // Demote too short weak words
+            if (wordLength <= 4 && freq <= MAX_FREQ * 2 / 3 /* heuristic... */) {
+                multiplyRate(100 * freq / MAX_FREQ, &totalFreq);
+            }
+            if (wordLength == 1) {
+                ++oneLengthCounter;
+            } else if (wordLength == 2) {
+                ++twoLengthCounter;
+            }
+            if (oneLengthCounter >= 2 || (oneLengthCounter + twoLengthCounter) >= 4) {
+                // Safety net to filter too many short words
+                return 0;
+            }
+        }
+        multiplyRate(MULTIPLE_WORDS_DEMOTION_RATE, &totalFreq);
+    }
 
     // This is a workaround to try offsetting the not-enough-demotion which will be done in
     // calcNormalizedScore in Utils.java.
@@ -923,19 +1016,128 @@
     if (isSpaceProximity) {
         // A word pair with one space proximity correction
         if (DEBUG_DICT) {
-            LOGI("Found a word pair with space proximity correction.");
+            AKLOGI("Found a word pair with space proximity correction.");
         }
         multiplyIntCapped(typedLetterMultiplier, &totalFreq);
         multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq);
     }
 
-    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
+    if (isSpaceProximity) {
+        multiplyRate(WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE, &totalFreq);
+    } else {
+        multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
+    }
 
     if (capitalizedWordDemotion) {
         multiplyRate(TWO_WORDS_CAPITALIZED_DEMOTION_RATE, &totalFreq);
     }
 
+    if (DEBUG_CORRECTION_FREQ) {
+        AKLOGI("Multiple words (%d, %d) (%d, %d) %d, %d", freqArray[0], freqArray[1],
+                wordLengthArray[0], wordLengthArray[1], capitalizedWordDemotion, totalFreq);
+        DUMP_WORD(word, wordLengthArray[0]);
+    }
+
     return totalFreq;
 }
 
+/* Damerau-Levenshtein distance */
+inline static int editDistanceInternal(
+        int* editDistanceTable, const unsigned short* before,
+        const int beforeLength, const unsigned short* after, const int afterLength) {
+    // dp[li][lo] dp[a][b] = dp[ a * lo + b]
+    int* dp = editDistanceTable;
+    const int li = beforeLength + 1;
+    const int lo = afterLength + 1;
+    for (int i = 0; i < li; ++i) {
+        dp[lo * i] = i;
+    }
+    for (int i = 0; i < lo; ++i) {
+        dp[i] = i;
+    }
+
+    for (int i = 0; i < li - 1; ++i) {
+        for (int j = 0; j < lo - 1; ++j) {
+            const uint32_t ci = toBaseLowerCase(before[i]);
+            const uint32_t co = toBaseLowerCase(after[j]);
+            const uint16_t cost = (ci == co) ? 0 : 1;
+            dp[(i + 1) * lo + (j + 1)] = min(dp[i * lo + (j + 1)] + 1,
+                    min(dp[(i + 1) * lo + j] + 1, dp[i * lo + j] + cost));
+            if (i > 0 && j > 0 && ci == toBaseLowerCase(after[j - 1])
+                    && co == toBaseLowerCase(before[i - 1])) {
+                dp[(i + 1) * lo + (j + 1)] = min(
+                        dp[(i + 1) * lo + (j + 1)], dp[(i - 1) * lo + (j - 1)] + cost);
+            }
+        }
+    }
+
+    if (DEBUG_EDIT_DISTANCE) {
+        AKLOGI("IN = %d, OUT = %d", beforeLength, afterLength);
+        for (int i = 0; i < li; ++i) {
+            for (int j = 0; j < lo; ++j) {
+                AKLOGI("EDIT[%d][%d], %d", i, j, dp[i * lo + j]);
+            }
+        }
+    }
+    return dp[li * lo - 1];
+}
+
+int Correction::RankingAlgorithm::editDistance(const unsigned short* before,
+        const int beforeLength, const unsigned short* after, const int afterLength) {
+    int table[(beforeLength + 1) * (afterLength + 1)];
+    return editDistanceInternal(table, before, beforeLength, after, afterLength);
+}
+
+
+// In dictionary.cpp, getSuggestion() method,
+// suggestion scores are computed using the below formula.
+// original score
+//  := pow(mTypedLetterMultiplier (this is defined 2),
+//         (the number of matched characters between typed word and suggested word))
+//     * (individual word's score which defined in the unigram dictionary,
+//         and this score is defined in range [0, 255].)
+// Then, the following processing is applied.
+//     - If the dictionary word is matched up to the point of the user entry
+//       (full match up to min(before.length(), after.length())
+//       => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
+//     - If the word is a true full match except for differences in accents or
+//       capitalization, then treat it as if the score was 255.
+//     - If before.length() == after.length()
+//       => multiply by mFullWordMultiplier (this is defined 2))
+// So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+// For historical reasons we ignore the 1.2 modifier (because the measure for a good
+// autocorrection threshold was done at a time when it didn't exist). This doesn't change
+// the result.
+// So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
+
+/* static */
+double Correction::RankingAlgorithm::calcNormalizedScore(const unsigned short* before,
+        const int beforeLength, const unsigned short* after, const int afterLength,
+        const int score) {
+    if (0 == beforeLength || 0 == afterLength) {
+        return 0;
+    }
+    const int distance = editDistance(before, beforeLength, after, afterLength);
+    int spaceCount = 0;
+    for (int i = 0; i < afterLength; ++i) {
+        if (after[i] == CODE_SPACE) {
+            ++spaceCount;
+        }
+    }
+
+    if (spaceCount == afterLength) {
+        return 0;
+    }
+
+    const double maxScore = score >= S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
+            * pow((double)TYPED_LETTER_MULTIPLIER,
+                    (double)min(beforeLength, afterLength - spaceCount)) * FULL_WORD_MULTIPLIER;
+
+    // add a weight based on edit distance.
+    // distance <= max(afterLength, beforeLength) == afterLength,
+    // so, 0 <= distance / afterLength <= 1
+    const double weight = 1.0 - (double) distance / afterLength;
+    return (score / maxScore) * weight;
+}
+
 } // namespace latinime
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
new file mode 100644
index 0000000..ee55c96
--- /dev/null
+++ b/native/jni/src/correction.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_CORRECTION_H
+#define LATINIME_CORRECTION_H
+
+#include <assert.h>
+#include <stdint.h>
+#include "correction_state.h"
+
+#include "defines.h"
+
+namespace latinime {
+
+class ProximityInfo;
+
+class Correction {
+ public:
+    typedef enum {
+        TRAVERSE_ALL_ON_TERMINAL,
+        TRAVERSE_ALL_NOT_ON_TERMINAL,
+        UNRELATED,
+        ON_TERMINAL,
+        NOT_ON_TERMINAL
+    } CorrectionType;
+
+    /////////////////////////
+    // static inline utils //
+    /////////////////////////
+
+    static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
+    static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
+        return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
+    }
+
+    static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
+    inline static void multiplyIntCapped(const int multiplier, int *base) {
+        const int temp = *base;
+        if (temp != S_INT_MAX) {
+            // Branch if multiplier == 2 for the optimization
+            if (multiplier < 0) {
+                if (DEBUG_DICT) {
+                    assert(false);
+                }
+                AKLOGI("--- Invalid multiplier: %d", multiplier);
+            } else if (multiplier == 0) {
+                *base = 0;
+            } else if (multiplier == 2) {
+                *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
+            } else {
+                // TODO: This overflow check gives a wrong answer when, for example,
+                //       temp = 2^16 + 1 and multiplier = 2^17 + 1.
+                //       Fix this behavior.
+                const int tempRetval = temp * multiplier;
+                *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
+            }
+        }
+    }
+
+    inline static int powerIntCapped(const int base, const int n) {
+        if (n <= 0) return 1;
+        if (base == 2) {
+            return n < 31 ? 1 << n : S_INT_MAX;
+        } else {
+            int ret = base;
+            for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret);
+            return ret;
+        }
+    }
+
+    inline static void multiplyRate(const int rate, int *freq) {
+        if (*freq != S_INT_MAX) {
+            if (*freq > 1000000) {
+                *freq /= 100;
+                multiplyIntCapped(rate, freq);
+            } else {
+                multiplyIntCapped(rate, freq);
+                *freq /= 100;
+            }
+        }
+    }
+
+    Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
+    void initCorrection(
+            const ProximityInfo *pi, const int inputLength, const int maxWordLength);
+    void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
+
+    // TODO: remove
+    void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
+            const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
+            const bool doAutoCompletion, const int maxErrors);
+    void checkState();
+    bool initProcessState(const int index);
+
+    int getInputIndex();
+
+    virtual ~Correction();
+    int getSpaceProximityPos() const {
+        return mSpaceProximityPos;
+    }
+    int getMissingSpacePos() const {
+        return mMissingSpacePos;
+    }
+
+    int getSkipPos() const {
+        return mSkipPos;
+    }
+
+    int getExcessivePos() const {
+        return mExcessivePos;
+    }
+
+    int getTransposedPos() const {
+        return mTransposedPos;
+    }
+
+    bool needsToPrune() const;
+
+    int getFreqForSplitMultipleWords(
+            const int *freqArray, const int *wordLengthArray, const int wordCount,
+            const bool isSpaceProximity, const unsigned short *word);
+    int getFinalFreq(const int freq, unsigned short **word, int* wordLength);
+    int getFinalFreqForSubQueue(const int freq, unsigned short **word, int* wordLength,
+            const int inputLength);
+
+    CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
+
+    /////////////////////////
+    // Tree helper methods
+    int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
+
+    inline int getTreeSiblingPos(const int index) const {
+        return mCorrectionStates[index].mSiblingPos;
+    }
+
+    inline void setTreeSiblingPos(const int index, const int pos) {
+        mCorrectionStates[index].mSiblingPos = pos;
+    }
+
+    inline int getTreeParentIndex(const int index) const {
+        return mCorrectionStates[index].mParentIndex;
+    }
+
+    class RankingAlgorithm {
+     public:
+        static int calculateFinalFreq(const int inputIndex, const int depth,
+                const int freq, int *editDistanceTable, const Correction* correction,
+                const int inputLength);
+        static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
+                const int wordCount, const Correction* correction, const bool isSpaceProximity,
+                const unsigned short *word);
+        static double calcNormalizedScore(const unsigned short* before, const int beforeLength,
+                const unsigned short* after, const int afterLength, const int score);
+        static int editDistance(const unsigned short* before,
+                const int beforeLength, const unsigned short* after, const int afterLength);
+     private:
+        static const int CODE_SPACE = ' ';
+        static const int MAX_INITIAL_SCORE = 255;
+        static const int TYPED_LETTER_MULTIPLIER = 2;
+        static const int FULL_WORD_MULTIPLIER = 2;
+    };
+
+ private:
+    inline void incrementInputIndex();
+    inline void incrementOutputIndex();
+    inline void startToTraverseAllNodes();
+    inline bool isQuote(const unsigned short c);
+    inline CorrectionType processSkipChar(
+            const int32_t c, const bool isTerminal, const bool inputIndexIncremented);
+    inline CorrectionType processUnrelatedCorrectionType();
+    inline void addCharToCurrentWord(const int32_t c);
+    inline int getFinalFreqInternal(const int freq, unsigned short **word, int* wordLength,
+            const int inputLength);
+
+    const int TYPED_LETTER_MULTIPLIER;
+    const int FULL_WORD_MULTIPLIER;
+    const ProximityInfo *mProximityInfo;
+
+    bool mUseFullEditDistance;
+    bool mDoAutoCompletion;
+    int mMaxEditDistance;
+    int mMaxDepth;
+    int mInputLength;
+    int mSpaceProximityPos;
+    int mMissingSpacePos;
+    int mTerminalInputIndex;
+    int mTerminalOutputIndex;
+    int mMaxErrors;
+
+    // The following arrays are state buffer.
+    unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
+    int mDistances[MAX_WORD_LENGTH_INTERNAL];
+
+    // Edit distance calculation requires a buffer with (N+1)^2 length for the input length N.
+    // Caveat: Do not create multiple tables per thread as this table eats up RAM a lot.
+    int mEditDistanceTable[(MAX_WORD_LENGTH_INTERNAL + 1) * (MAX_WORD_LENGTH_INTERNAL + 1)];
+
+    CorrectionState mCorrectionStates[MAX_WORD_LENGTH_INTERNAL];
+
+    // The following member variables are being used as cache values of the correction state.
+    bool mNeedsToTraverseAllNodes;
+    int mOutputIndex;
+    int mInputIndex;
+
+    int mEquivalentCharCount;
+    int mProximityCount;
+    int mExcessiveCount;
+    int mTransposedCount;
+    int mSkippedCount;
+
+    int mTransposedPos;
+    int mExcessivePos;
+    int mSkipPos;
+
+    bool mLastCharExceeded;
+
+    bool mMatching;
+    bool mProximityMatching;
+    bool mAdditionalProximityMatching;
+    bool mExceeding;
+    bool mTransposing;
+    bool mSkipping;
+
+};
+} // namespace latinime
+#endif // LATINIME_CORRECTION_H
diff --git a/native/src/correction_state.h b/native/jni/src/correction_state.h
similarity index 95%
rename from native/src/correction_state.h
rename to native/jni/src/correction_state.h
index c04146e..5b2cbd3 100644
--- a/native/src/correction_state.h
+++ b/native/jni/src/correction_state.h
@@ -47,9 +47,9 @@
     bool mExceeding;
     bool mSkipping;
     bool mProximityMatching;
+    bool mAdditionalProximityMatching;
 
     bool mNeedsToTraverseAllNodes;
-
 };
 
 inline static void initCorrectionState(CorrectionState *state, const int rootPos,
@@ -77,7 +77,7 @@
     state->mTransposing = false;
     state->mExceeding = false;
     state->mSkipping = false;
-
+    state->mAdditionalProximityMatching = false;
 }
 
 } // namespace latinime
diff --git a/native/src/debug.h b/native/jni/src/debug.h
similarity index 95%
rename from native/src/debug.h
rename to native/jni/src/debug.h
index 38b2f10..b13052c 100644
--- a/native/src/debug.h
+++ b/native/jni/src/debug.h
@@ -42,7 +42,7 @@
 static inline void LOGI_S16(unsigned short* string, const unsigned int length) {
     unsigned char tmp_buffer[length];
     convertToUnibyteString(string, tmp_buffer, length);
-    LOGI(">> %s", tmp_buffer);
+    AKLOGI(">> %s", tmp_buffer);
     // The log facility is throwing out log that comes too fast. The following
     // is a dirty way of slowing down processing so that we can see all log.
     // TODO : refactor this in a blocking log or something.
@@ -53,7 +53,7 @@
         unsigned char c) {
     unsigned char tmp_buffer[length+1];
     convertToUnibyteStringAndReplaceLastChar(string, tmp_buffer, length, c);
-    LOGI(">> %s", tmp_buffer);
+    AKLOGI(">> %s", tmp_buffer);
     // Likewise
     // usleep(10);
 }
@@ -64,7 +64,7 @@
     buf[codesSize] = 0;
     while (--codesSize >= 0)
         buf[codesSize] = (unsigned char)codes[codesSize * MAX_PROXIMITY_CHARS];
-    LOGI("%s, WORD = %s", tag, buf);
+    AKLOGI("%s, WORD = %s", tag, buf);
 
     free(buf);
 }
diff --git a/native/src/defines.h b/native/jni/src/defines.h
similarity index 68%
rename from native/src/defines.h
rename to native/jni/src/defines.h
index ef1beb9..e882c37 100644
--- a/native/src/defines.h
+++ b/native/jni/src/defines.h
@@ -20,15 +20,31 @@
 
 #if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
 #include <cutils/log.h>
+#define AKLOGE ALOGE
+#define AKLOGI ALOGI
+
+#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0)
+
+static inline void dumpWord(const unsigned short* word, const int length) {
+    static char charBuf[50];
+
+    for (int i = 0; i < length; ++i) {
+        charBuf[i] = word[i];
+    }
+    charBuf[length] = 0;
+    AKLOGI("[ %s ]", charBuf);
+}
+
 #else
-#define LOGE(fmt, ...)
-#define LOGI(fmt, ...)
+#define AKLOGE(fmt, ...)
+#define AKLOGI(fmt, ...)
+#define DUMP_WORD(word, length)
 #endif
 
 #ifdef FLAG_DO_PROFILE
 // Profiler
-#include <cutils/log.h>
 #include <time.h>
+
 #define PROF_BUF_SIZE 100
 static double profile_buf[PROF_BUF_SIZE];
 static double profile_old[PROF_BUF_SIZE];
@@ -42,10 +58,10 @@
 #define PROF_CLOSE               do { PROF_END(PROF_BUF_SIZE - 1); PROF_OUTALL; } while(0)
 #define PROF_END(prof_buf_id)    profile_buf[prof_buf_id] += ((clock()) - profile_old[prof_buf_id])
 #define PROF_CLOCKOUT(prof_buf_id) \
-        LOGI("%s : clock is %f", __FUNCTION__, (clock() - profile_old[prof_buf_id]))
-#define PROF_OUTALL              do { LOGI("--- %s ---", __FUNCTION__); prof_out(); } while(0)
+        AKLOGI("%s : clock is %f", __FUNCTION__, (clock() - profile_old[prof_buf_id]))
+#define PROF_OUTALL              do { AKLOGI("--- %s ---", __FUNCTION__); prof_out(); } while(0)
 
-static void prof_reset(void) {
+static inline void prof_reset(void) {
     for (int i = 0; i < PROF_BUF_SIZE; ++i) {
         profile_buf[i] = 0;
         profile_old[i] = 0;
@@ -53,11 +69,11 @@
     }
 }
 
-static void prof_out(void) {
+static inline void prof_out(void) {
     if (profile_counter[PROF_BUF_SIZE - 1] != 1) {
-        LOGI("Error: You must call PROF_OPEN before PROF_CLOSE.");
+        AKLOGI("Error: You must call PROF_OPEN before PROF_CLOSE.");
     }
-    LOGI("Total time is %6.3f ms.",
+    AKLOGI("Total time is %6.3f ms.",
             profile_buf[PROF_BUF_SIZE - 1] * 1000 / (double)CLOCKS_PER_SEC);
     double all = 0;
     for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) {
@@ -66,7 +82,7 @@
     if (all == 0) all = 1;
     for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) {
         if (profile_buf[i] != 0) {
-            LOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.",
+            AKLOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.",
                     i, (profile_buf[i] * 100 / all),
                     profile_buf[i] * 1000 / (double)CLOCKS_PER_SEC, profile_counter[i]);
         }
@@ -98,21 +114,11 @@
 #define DEBUG_SHOW_FOUND_WORD false
 #define DEBUG_NODE DEBUG_DICT_FULL
 #define DEBUG_TRACE DEBUG_DICT_FULL
-#define DEBUG_PROXIMITY_INFO true
+#define DEBUG_PROXIMITY_INFO false
+#define DEBUG_PROXIMITY_CHARS false
 #define DEBUG_CORRECTION false
-#define DEBUG_CORRECTION_FREQ true
-
-#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0)
-
-static char charBuf[50];
-
-static void dumpWord(const unsigned short* word, const int length) {
-    for (int i = 0; i < length; ++i) {
-        charBuf[i] = word[i];
-    }
-    charBuf[length] = 0;
-    LOGI("[ %s ]", charBuf);
-}
+#define DEBUG_CORRECTION_FREQ false
+#define DEBUG_WORDS_PRIORITY_QUEUE false
 
 #else // FLAG_DBG
 
@@ -123,10 +129,11 @@
 #define DEBUG_NODE false
 #define DEBUG_TRACE false
 #define DEBUG_PROXIMITY_INFO false
+#define DEBUG_PROXIMITY_CHARS false
 #define DEBUG_CORRECTION false
 #define DEBUG_CORRECTION_FREQ false
+#define DEBUG_WORDS_PRIORITY_QUEUE false
 
-#define DUMP_WORD(word, length)
 
 #endif // FLAG_DBG
 
@@ -157,64 +164,91 @@
 #define FLAG_BIGRAM_FREQ 0x7F
 
 #define DICTIONARY_VERSION_MIN 200
-// TODO: remove this constant when the switch to the new dict format is over
-#define DICTIONARY_HEADER_SIZE 2
-#define NEW_DICTIONARY_HEADER_SIZE 5
 #define NOT_VALID_WORD -99
 #define NOT_A_CHARACTER -1
 #define NOT_A_DISTANCE -1
+#define NOT_A_COORDINATE -1
 #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
-#define NOT_A_INDEX -1
+#define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO -4
+#define NOT_AN_INDEX -1
+#define NOT_A_FREQUENCY -1
 
 #define KEYCODE_SPACE ' '
 
 #define CALIBRATE_SCORE_BY_TOUCH_COORDINATES true
 
 #define SUGGEST_WORDS_WITH_MISSING_CHARACTER true
-#define SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER true
 #define SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER true
 #define SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS true
-#define SUGGEST_WORDS_WITH_SPACE_PROXIMITY true
+#define SUGGEST_MULTIPLE_WORDS true
 
 // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent.
 #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 80
 #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X 12
-#define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 67
+#define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 58
+#define WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE 50
 #define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75
 #define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75
-#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
+#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 70
 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120
 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
+#define WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE 70
 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
-#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 160
+#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 148
+#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER 3
 #define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 45
 #define INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE 70
 #define FIRST_CHAR_DIFFERENT_DEMOTION_RATE 96
 #define TWO_WORDS_CAPITALIZED_DEMOTION_RATE 50
+#define TWO_WORDS_CORRECTION_DEMOTION_BASE 80
+#define TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER 1
 #define ZERO_DISTANCE_PROMOTION_RATE 110
 #define NEUTRAL_SCORE_SQUARED_RADIUS 8.0f
 #define HALF_SCORE_SQUARED_RADIUS 32.0f
+#define MAX_FREQ 255
 
-// This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java
+// This must be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java
 // This is only used for the size of array. Not to be used in c functions.
 #define MAX_WORD_LENGTH_INTERNAL 48
 
+// This must be equal to ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE in KeyDetector.java
+#define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
+
+// Word limit for sub queues used in WordsPriorityQueuePool.  Sub queues are temporary queues used
+// for better performance.
+// Holds up to 1 candidate for each word
+#define SUB_QUEUE_MAX_WORDS 1
+#define SUB_QUEUE_MAX_COUNT 10
+#define SUB_QUEUE_MIN_WORD_LENGTH 4
+#define MULTIPLE_WORDS_SUGGESTION_MAX_WORDS 10
+#define MULTIPLE_WORDS_DEMOTION_RATE 80
+#define MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION 6
+
+#define TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD 0.39
+#define START_TWO_WORDS_CORRECTION_THRESHOLD 0.22
+
 #define MAX_DEPTH_MULTIPLIER 3
 
-// TODO: Reduce this constant if possible; check the maximum number of umlauts in the same German
-// word in the dictionary
-#define DEFAULT_MAX_UMLAUT_SEARCH_DEPTH 5
+#define FIRST_WORD_INDEX 0
+
+// TODO: Reduce this constant if possible; check the maximum number of digraphs in the same
+// word in the dictionary for languages with digraphs, like German and French
+#define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5
 
 // Minimum suggest depth for one word for all cases except for missing space suggestions.
 #define MIN_SUGGEST_DEPTH 1
-#define MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION 3
+#define MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION 3
 #define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3
 
-#define min(a,b) ((a)<(b)?(a):(b))
-#define max(a,b) ((a)>(b)?(a):(b))
+template<typename T> inline T min(T a, T b) { return a < b ? a : b; }
+template<typename T> inline T max(T a, T b) { return a > b ? a : b; }
 
 // The ratio of neutral area radius to sweet spot radius.
 #define NEUTRAL_AREA_RADIUS_RATIO 1.3f
 
+// DEBUG
+#define INPUTLENGTH_FOR_DEBUG -1
+#define MIN_OUTPUT_INDEX_FOR_DEBUG -1
+
 #endif // LATINIME_DEFINES_H
diff --git a/native/src/dictionary.cpp b/native/jni/src/dictionary.cpp
similarity index 65%
rename from native/src/dictionary.cpp
rename to native/jni/src/dictionary.cpp
index a49769b..981a983 100644
--- a/native/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -19,6 +19,7 @@
 
 #define LOG_TAG "LatinIME: dictionary.cpp"
 
+#include "binary_format.h"
 #include "dictionary.h"
 
 namespace latinime {
@@ -26,33 +27,35 @@
 // TODO: Change the type of all keyCodes to uint32_t
 Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
         int typedLetterMultiplier, int fullWordMultiplier,
-        int maxWordLength, int maxWords, int maxAlternatives)
+        int maxWordLength, int maxWords)
     : mDict((unsigned char*) dict), mDictSize(dictSize),
     mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
     // Checks whether it has the latest dictionary or the old dictionary
     IS_LATEST_DICT_VERSION((((unsigned char*) dict)[0] & 0xFF) >= DICTIONARY_VERSION_MIN) {
     if (DEBUG_DICT) {
         if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
-            LOGI("Max word length (%d) is greater than %d",
+            AKLOGI("Max word length (%d) is greater than %d",
                     maxWordLength, MAX_WORD_LENGTH_INTERNAL);
-            LOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
+            AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
         }
     }
-    mUnigramDictionary = new UnigramDictionary(mDict, typedLetterMultiplier, fullWordMultiplier,
-            maxWordLength, maxWords, maxAlternatives, IS_LATEST_DICT_VERSION);
-    mBigramDictionary = new BigramDictionary(mDict, maxWordLength, maxAlternatives,
-            IS_LATEST_DICT_VERSION, hasBigram(), this);
+    mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
+    mWordsPriorityQueuePool = new WordsPriorityQueuePool(
+            maxWords, SUB_QUEUE_MAX_WORDS, maxWordLength);
+    const unsigned int headerSize = BinaryFormat::getHeaderSize(mDict);
+    mUnigramDictionary = new UnigramDictionary(mDict + headerSize, typedLetterMultiplier,
+            fullWordMultiplier, maxWordLength, maxWords, IS_LATEST_DICT_VERSION);
+    mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength,
+            IS_LATEST_DICT_VERSION, true /* hasBigram */, this);
 }
 
 Dictionary::~Dictionary() {
+    delete mCorrection;
+    delete mWordsPriorityQueuePool;
     delete mUnigramDictionary;
     delete mBigramDictionary;
 }
 
-bool Dictionary::hasBigram() {
-    return ((mDict[1] & 0xFF) == 1);
-}
-
 bool Dictionary::isValidWord(unsigned short *word, int length) {
     return mUnigramDictionary->isValidWord(word, length);
 }
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
new file mode 100644
index 0000000..139d3f0
--- /dev/null
+++ b/native/jni/src/dictionary.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      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.
+ */
+
+#ifndef LATINIME_DICTIONARY_H
+#define LATINIME_DICTIONARY_H
+
+#include "bigram_dictionary.h"
+#include "char_utils.h"
+#include "correction.h"
+#include "defines.h"
+#include "proximity_info.h"
+#include "unigram_dictionary.h"
+#include "words_priority_queue_pool.h"
+
+namespace latinime {
+
+class Dictionary {
+ public:
+    Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler,
+            int fullWordMultiplier, int maxWordLength, int maxWords);
+
+    int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
+            int *codes, int codesSize, int flags, unsigned short *outWords, int *frequencies) {
+        return mUnigramDictionary->getSuggestions(proximityInfo, mWordsPriorityQueuePool,
+                mCorrection, xcoordinates, ycoordinates, codes,
+                codesSize, flags, outWords, frequencies);
+    }
+
+    // TODO: Call mBigramDictionary instead of mUnigramDictionary
+    int getBigrams(unsigned short *word, int length, int *codes, int codesSize,
+            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams) {
+        return mBigramDictionary->getBigrams(word, length, codes, codesSize, outWords, frequencies,
+                maxWordLength, maxBigrams);
+    }
+
+    bool isValidWord(unsigned short *word, int length);
+    void *getDict() { return (void *)mDict; }
+    int getDictSize() { return mDictSize; }
+    int getMmapFd() { return mMmapFd; }
+    int getDictBufAdjust() { return mDictBufAdjust; }
+    ~Dictionary();
+
+    // public static utility methods
+    // static inline methods should be defined in the header file
+    static int wideStrLen(unsigned short *str);
+
+ private:
+    bool hasBigram();
+
+    const unsigned char *mDict;
+
+    // Used only for the mmap version of dictionary loading, but we use these as dummy variables
+    // also for the malloc version.
+    const int mDictSize;
+    const int mMmapFd;
+    const int mDictBufAdjust;
+
+    const bool IS_LATEST_DICT_VERSION;
+    UnigramDictionary *mUnigramDictionary;
+    BigramDictionary *mBigramDictionary;
+    WordsPriorityQueuePool *mWordsPriorityQueuePool;
+    Correction *mCorrection;
+};
+
+// public static utility methods
+// static inline methods should be defined in the header file
+inline int Dictionary::wideStrLen(unsigned short *str) {
+    if (!str) return 0;
+    unsigned short *end = str;
+    while (*end)
+        end++;
+    return end - str;
+}
+} // namespace latinime
+
+#endif // LATINIME_DICTIONARY_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
new file mode 100644
index 0000000..c00c4c2
--- /dev/null
+++ b/native/jni/src/proximity_info.cpp
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string>
+
+#define LOG_TAG "LatinIME: proximity_info.cpp"
+
+#include "additional_proximity_chars.h"
+#include "dictionary.h"
+#include "proximity_info.h"
+
+namespace latinime {
+
+inline void copyOrFillZero(void *to, const void *from, size_t size) {
+    if (from) {
+        memcpy(to, from, size);
+    } else {
+        memset(to, 0, size);
+    }
+}
+
+ProximityInfo::ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+        const int keyboardWidth, const int keyboardHeight, const int gridWidth,
+        const int gridHeight, const int mostCommonKeyWidth,
+        const int32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
+        const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
+        const int32_t *keyCharCodes, const float *sweetSpotCenterXs, const float *sweetSpotCenterYs,
+        const float *sweetSpotRadii)
+        : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
+          KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
+          MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
+          CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
+          CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
+          KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
+          HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
+                  && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
+                  && sweetSpotCenterYs && sweetSpotRadii),
+          mLocaleStr(localeStr),
+          mInputXCoordinates(0), mInputYCoordinates(0),
+          mTouchPositionCorrectionEnabled(false) {
+    const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
+    mProximityCharsArray = new int32_t[proximityGridLength];
+    mInputCodes = new int32_t[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH_INTERNAL];
+    if (DEBUG_PROXIMITY_INFO) {
+        AKLOGI("Create proximity info array %d", proximityGridLength);
+    }
+    memcpy(mProximityCharsArray, proximityCharsArray,
+            proximityGridLength * sizeof(mProximityCharsArray[0]));
+    const int normalizedSquaredDistancesLength =
+            MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH_INTERNAL;
+    mNormalizedSquaredDistances = new int[normalizedSquaredDistancesLength];
+    for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
+        mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
+    }
+
+    copyOrFillZero(mKeyXCoordinates, keyXCoordinates, KEY_COUNT * sizeof(mKeyXCoordinates[0]));
+    copyOrFillZero(mKeyYCoordinates, keyYCoordinates, KEY_COUNT * sizeof(mKeyYCoordinates[0]));
+    copyOrFillZero(mKeyWidths, keyWidths, KEY_COUNT * sizeof(mKeyWidths[0]));
+    copyOrFillZero(mKeyHeights, keyHeights, KEY_COUNT * sizeof(mKeyHeights[0]));
+    copyOrFillZero(mKeyCharCodes, keyCharCodes, KEY_COUNT * sizeof(mKeyCharCodes[0]));
+    copyOrFillZero(mSweetSpotCenterXs, sweetSpotCenterXs,
+            KEY_COUNT * sizeof(mSweetSpotCenterXs[0]));
+    copyOrFillZero(mSweetSpotCenterYs, sweetSpotCenterYs,
+            KEY_COUNT * sizeof(mSweetSpotCenterYs[0]));
+    copyOrFillZero(mSweetSpotRadii, sweetSpotRadii, KEY_COUNT * sizeof(mSweetSpotRadii[0]));
+
+    initializeCodeToKeyIndex();
+}
+
+// Build the reversed look up table from the char code to the index in mKeyXCoordinates,
+// mKeyYCoordinates, mKeyWidths, mKeyHeights, mKeyCharCodes.
+void ProximityInfo::initializeCodeToKeyIndex() {
+    memset(mCodeToKeyIndex, -1, (MAX_CHAR_CODE + 1) * sizeof(mCodeToKeyIndex[0]));
+    for (int i = 0; i < KEY_COUNT; ++i) {
+        const int code = mKeyCharCodes[i];
+        if (0 <= code && code <= MAX_CHAR_CODE) {
+            mCodeToKeyIndex[code] = i;
+        }
+    }
+}
+
+ProximityInfo::~ProximityInfo() {
+    delete[] mNormalizedSquaredDistances;
+    delete[] mProximityCharsArray;
+    delete[] mInputCodes;
+}
+
+inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
+    return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
+            * MAX_PROXIMITY_CHARS_SIZE;
+}
+
+bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
+    if (x < 0 || y < 0) {
+        if (DEBUG_DICT) {
+            AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
+            assert(false);
+        }
+        return false;
+    }
+
+    const int startIndex = getStartIndexFromCoordinates(x, y);
+    if (DEBUG_PROXIMITY_INFO) {
+        AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
+    }
+    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+        if (DEBUG_PROXIMITY_INFO) {
+            AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
+        }
+        if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool ProximityInfo::isOnKey(const int keyId, const int x, const int y) const {
+    if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+    const int left = mKeyXCoordinates[keyId];
+    const int top = mKeyYCoordinates[keyId];
+    const int right = left + mKeyWidths[keyId] + 1;
+    const int bottom = top + mKeyHeights[keyId];
+    return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
+}
+
+int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
+    if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+    const int left = mKeyXCoordinates[keyId];
+    const int top = mKeyYCoordinates[keyId];
+    const int right = left + mKeyWidths[keyId];
+    const int bottom = top + mKeyHeights[keyId];
+    const int edgeX = x < left ? left : (x > right ? right : x);
+    const int edgeY = y < top ? top : (y > bottom ? bottom : y);
+    const int dx = x - edgeX;
+    const int dy = y - edgeY;
+    return dx * dx + dy * dy;
+}
+
+void ProximityInfo::calculateNearbyKeyCodes(
+        const int x, const int y, const int32_t primaryKey, int *inputCodes) const {
+    int insertPos = 0;
+    inputCodes[insertPos++] = primaryKey;
+    const int startIndex = getStartIndexFromCoordinates(x, y);
+    if (startIndex >= 0) {
+        for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+            const int32_t c = mProximityCharsArray[startIndex + i];
+            if (c < KEYCODE_SPACE || c == primaryKey) {
+                continue;
+            }
+            const int keyIndex = getKeyIndex(c);
+            const bool onKey = isOnKey(keyIndex, x, y);
+            const int distance = squaredDistanceToEdge(keyIndex, x, y);
+            if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
+                inputCodes[insertPos++] = c;
+                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                    if (DEBUG_DICT) {
+                        assert(false);
+                    }
+                    return;
+                }
+            }
+        }
+        const int additionalProximitySize =
+                AdditionalProximityChars::getAdditionalCharsSize(&mLocaleStr, primaryKey);
+        if (additionalProximitySize > 0) {
+            inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+            if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                if (DEBUG_DICT) {
+                    assert(false);
+                }
+                return;
+            }
+
+            const int32_t* additionalProximityChars =
+                    AdditionalProximityChars::getAdditionalChars(&mLocaleStr, primaryKey);
+            for (int j = 0; j < additionalProximitySize; ++j) {
+                const int32_t ac = additionalProximityChars[j];
+                int k = 0;
+                for (; k < insertPos; ++k) {
+                    if ((int)ac == inputCodes[k]) {
+                        break;
+                    }
+                }
+                if (k < insertPos) {
+                    continue;
+                }
+                inputCodes[insertPos++] = ac;
+                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                    if (DEBUG_DICT) {
+                        assert(false);
+                    }
+                    return;
+                }
+            }
+        }
+    }
+    // Add a delimiter for the proximity characters
+    for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+        inputCodes[i] = NOT_A_CODE;
+    }
+}
+
+void ProximityInfo::setInputParams(const int32_t* inputCodes, const int inputLength,
+        const int* xCoordinates, const int* yCoordinates) {
+    memset(mInputCodes, 0,
+            MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE * sizeof(mInputCodes[0]));
+
+    for (int i = 0; i < inputLength; ++i) {
+        const int32_t primaryKey = inputCodes[i];
+        const int x = xCoordinates[i];
+        const int y = yCoordinates[i];
+        int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE];
+        calculateNearbyKeyCodes(x, y, primaryKey, proximities);
+    }
+
+    if (DEBUG_PROXIMITY_CHARS) {
+        for (int i = 0; i < inputLength; ++i) {
+            AKLOGI("---");
+            for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; ++j) {
+                int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE + j];
+                int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE + j];
+                icc+= 0;
+                icfjc += 0;
+                AKLOGI("--- (%d)%c,%c", i, icc, icfjc);
+                AKLOGI("---             A<%d>,B<%d>", icc, icfjc);
+            }
+        }
+    }
+    //Keep for debug, sorry
+    //for (int i = 0; i < MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE; ++i) {
+    //if (i < inputLength * MAX_PROXIMITY_CHARS_SIZE) {
+    //mInputCodes[i] = mInputCodesFromJava[i];
+    //} else {
+    // mInputCodes[i] = 0;
+    // }
+    //}
+    mInputXCoordinates = xCoordinates;
+    mInputYCoordinates = yCoordinates;
+    mTouchPositionCorrectionEnabled =
+            HAS_TOUCH_POSITION_CORRECTION_DATA && xCoordinates && yCoordinates;
+    mInputLength = inputLength;
+    for (int i = 0; i < inputLength; ++i) {
+        mPrimaryInputWord[i] = getPrimaryCharAt(i);
+    }
+    mPrimaryInputWord[inputLength] = 0;
+    if (DEBUG_PROXIMITY_CHARS) {
+        AKLOGI("--- setInputParams");
+    }
+    for (int i = 0; i < mInputLength; ++i) {
+        const int *proximityChars = getProximityCharsAt(i);
+        const int primaryKey = proximityChars[0];
+        const int x = xCoordinates[i];
+        const int y = yCoordinates[i];
+        if (DEBUG_PROXIMITY_CHARS) {
+            int a = x + y + primaryKey;
+            a += 0;
+            AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
+            // Keep debug code just in case
+            //int proximities[50];
+            //for (int m = 0; m < 50; ++m) {
+            //proximities[m] = 0;
+            //}
+            //calculateNearbyKeyCodes(x, y, primaryKey, proximities);
+            //for (int l = 0; l < 50 && proximities[l] > 0; ++l) {
+            //if (DEBUG_PROXIMITY_CHARS) {
+            //AKLOGI("--- native Proximity (%d) = %c", l, proximities[l]);
+            //}
+            //}
+        }
+        for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE && proximityChars[j] > 0; ++j) {
+            const int currentChar = proximityChars[j];
+            const float squaredDistance = hasInputCoordinates()
+                    ? calculateNormalizedSquaredDistance(getKeyIndex(currentChar), i)
+                    : NOT_A_DISTANCE_FLOAT;
+            if (squaredDistance >= 0.0f) {
+                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] =
+                        (int)(squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
+            } else {
+                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] = (j == 0)
+                        ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO
+                        : PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
+            }
+            if (DEBUG_PROXIMITY_CHARS) {
+                AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+            }
+        }
+    }
+}
+
+inline float square(const float x) { return x * x; }
+
+float ProximityInfo::calculateNormalizedSquaredDistance(
+        const int keyIndex, const int inputIndex) const {
+    if (keyIndex == NOT_AN_INDEX) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
+    if (!hasSweetSpotData(keyIndex)) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
+    if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
+    const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, inputIndex);
+    const float squaredRadius = square(mSweetSpotRadii[keyIndex]);
+    return squaredDistance / squaredRadius;
+}
+
+bool ProximityInfo::hasInputCoordinates() const {
+    return mInputXCoordinates && mInputYCoordinates;
+}
+
+int ProximityInfo::getKeyIndex(const int c) const {
+    if (KEY_COUNT == 0) {
+        // We do not have the coordinate data
+        return NOT_AN_INDEX;
+    }
+    const unsigned short baseLowerC = toBaseLowerCase(c);
+    if (baseLowerC > MAX_CHAR_CODE) {
+        return NOT_AN_INDEX;
+    }
+    return mCodeToKeyIndex[baseLowerC];
+}
+
+float ProximityInfo::calculateSquaredDistanceFromSweetSpotCenter(
+        const int keyIndex, const int inputIndex) const {
+    const float sweetSpotCenterX = mSweetSpotCenterXs[keyIndex];
+    const float sweetSpotCenterY = mSweetSpotCenterYs[keyIndex];
+    const float inputX = (float)mInputXCoordinates[inputIndex];
+    const float inputY = (float)mInputYCoordinates[inputIndex];
+    return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
+}
+
+inline const int* ProximityInfo::getProximityCharsAt(const int index) const {
+    return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE);
+}
+
+unsigned short ProximityInfo::getPrimaryCharAt(const int index) const {
+    return getProximityCharsAt(index)[0];
+}
+
+inline bool ProximityInfo::existsCharInProximityAt(const int index, const int c) const {
+    const int *chars = getProximityCharsAt(index);
+    int i = 0;
+    while (chars[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE) {
+        if (chars[i++] == c) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool ProximityInfo::existsAdjacentProximityChars(const int index) const {
+    if (index < 0 || index >= mInputLength) return false;
+    const int currentChar = getPrimaryCharAt(index);
+    const int leftIndex = index - 1;
+    if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
+        return true;
+    }
+    const int rightIndex = index + 1;
+    if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
+        return true;
+    }
+    return false;
+}
+
+// In the following function, c is the current character of the dictionary word
+// currently examined.
+// currentChars is an array containing the keys close to the character the
+// user actually typed at the same position. We want to see if c is in it: if so,
+// then the word contains at that position a character close to what the user
+// typed.
+// What the user typed is actually the first character of the array.
+// proximityIndex is a pointer to the variable where getMatchedProximityId returns
+// the index of c in the proximity chars of the input index.
+// Notice : accented characters do not have a proximity list, so they are alone
+// in their list. The non-accented version of the character should be considered
+// "close", but not the other keys close to the non-accented version.
+ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int index,
+        const unsigned short c, const bool checkProximityChars, int *proximityIndex) const {
+    const int *currentChars = getProximityCharsAt(index);
+    const int firstChar = currentChars[0];
+    const unsigned short baseLowerC = toBaseLowerCase(c);
+
+    // The first char in the array is what user typed. If it matches right away,
+    // that means the user typed that same char for this pos.
+    if (firstChar == baseLowerC || firstChar == c) {
+        return EQUIVALENT_CHAR;
+    }
+
+    if (!checkProximityChars) return UNRELATED_CHAR;
+
+    // If the non-accented, lowercased version of that first character matches c,
+    // then we have a non-accented version of the accented character the user
+    // typed. Treat it as a close char.
+    if (toBaseLowerCase(firstChar) == baseLowerC)
+        return NEAR_PROXIMITY_CHAR;
+
+    // Not an exact nor an accent-alike match: search the list of close keys
+    int j = 1;
+    while (j < MAX_PROXIMITY_CHARS_SIZE
+            && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+        const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+        if (matched) {
+            if (proximityIndex) {
+                *proximityIndex = j;
+            }
+            return NEAR_PROXIMITY_CHAR;
+        }
+        ++j;
+    }
+    if (j < MAX_PROXIMITY_CHARS_SIZE
+            && currentChars[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+        ++j;
+        while (j < MAX_PROXIMITY_CHARS_SIZE
+                && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+            const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+            if (matched) {
+                if (proximityIndex) {
+                    *proximityIndex = j;
+                }
+                return ADDITIONAL_PROXIMITY_CHAR;
+            }
+            ++j;
+        }
+    }
+
+    // Was not included, signal this as an unrelated character.
+    return UNRELATED_CHAR;
+}
+
+bool ProximityInfo::sameAsTyped(const unsigned short *word, int length) const {
+    if (length != mInputLength) {
+        return false;
+    }
+    const int *inputCodes = mInputCodes;
+    while (length--) {
+        if ((unsigned int) *inputCodes != (unsigned int) *word) {
+            return false;
+        }
+        inputCodes += MAX_PROXIMITY_CHARS_SIZE;
+        word++;
+    }
+    return true;
+}
+
+const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
+const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
+const int ProximityInfo::MAX_KEY_COUNT_IN_A_KEYBOARD;
+const int ProximityInfo::MAX_CHAR_CODE;
+
+} // namespace latinime
diff --git a/native/src/proximity_info.h b/native/jni/src/proximity_info.h
similarity index 78%
rename from native/src/proximity_info.h
rename to native/jni/src/proximity_info.h
index 35e354c..c1eefea 100644
--- a/native/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -18,6 +18,7 @@
 #define LATINIME_PROXIMITY_INFO_H
 
 #include <stdint.h>
+#include <string>
 
 #include "defines.h"
 
@@ -26,7 +27,7 @@
 class Correction;
 
 class ProximityInfo {
-public:
+ public:
     static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10;
     static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR =
             1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
@@ -38,25 +39,28 @@
         // It is a char located nearby on the keyboard
         NEAR_PROXIMITY_CHAR,
         // It is an unrelated char
-        UNRELATED_CHAR
+        UNRELATED_CHAR,
+        // Additional proximity char which can differ by language.
+        ADDITIONAL_PROXIMITY_CHAR
     } ProximityType;
 
-    ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
-            const int keybaordHeight, const int gridWidth, const int gridHeight,
-            const uint32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
+    ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+            const int keyboardWidth, const int keybaordHeight, const int gridWidth,
+            const int gridHeight, const int mostCommonkeyWidth,
+            const int32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
             const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
             const int32_t *keyCharCodes, const float *sweetSpotCenterXs,
             const float *sweetSpotCenterYs, const float *sweetSpotRadii);
     ~ProximityInfo();
     bool hasSpaceProximity(const int x, const int y) const;
-    void setInputParams(const int* inputCodes, const int inputLength,
+    void setInputParams(const int32_t *inputCodes, const int inputLength,
             const int *xCoordinates, const int *yCoordinates);
     const int* getProximityCharsAt(const int index) const;
     unsigned short getPrimaryCharAt(const int index) const;
     bool existsCharInProximityAt(const int index, const int c) const;
     bool existsAdjacentProximityChars(const int index) const;
     ProximityType getMatchedProximityId(const int index, const unsigned short c,
-            const bool checkProximityChars, int *proximityIndex = NULL) const;
+            const bool checkProximityChars, int *proximityIndex = 0) const;
     int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const {
         return mNormalizedSquaredDistances[inputIndex * MAX_PROXIMITY_CHARS_SIZE + proximityIndex];
     }
@@ -68,38 +72,49 @@
         return mTouchPositionCorrectionEnabled;
     }
 
-private:
+ private:
     // The max number of the keys in one keyboard layout
     static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
     // The upper limit of the char code in mCodeToKeyIndex
     static const int MAX_CHAR_CODE = 127;
+    static const float NOT_A_DISTANCE_FLOAT = -1.0f;
+    static const int NOT_A_CODE = -1;
 
     int getStartIndexFromCoordinates(const int x, const int y) const;
     void initializeCodeToKeyIndex();
     float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
     float calculateSquaredDistanceFromSweetSpotCenter(
             const int keyIndex, const int inputIndex) const;
+    bool hasInputCoordinates() const;
     int getKeyIndex(const int c) const;
     bool hasSweetSpotData(const int keyIndex) const {
         // When there are no calibration data for a key,
         // the radius of the key is assigned to zero.
         return mSweetSpotRadii[keyIndex] > 0.0;
     }
+    bool isOnKey(const int keyId, const int x, const int y) const;
+    int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
+    void calculateNearbyKeyCodes(
+            const int x, const int y, const int32_t primaryKey, int *inputCodes) const;
 
     const int MAX_PROXIMITY_CHARS_SIZE;
     const int KEYBOARD_WIDTH;
     const int KEYBOARD_HEIGHT;
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
+    const int MOST_COMMON_KEY_WIDTH_SQUARE;
     const int CELL_WIDTH;
     const int CELL_HEIGHT;
     const int KEY_COUNT;
     const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
-    const int *mInputCodes;
+    const std::string mLocaleStr;
+    // TODO: remove this
+    const int *mInputCodesFromJava;
+    int32_t *mInputCodes;
     const int *mInputXCoordinates;
     const int *mInputYCoordinates;
     bool mTouchPositionCorrectionEnabled;
-    uint32_t *mProximityCharsArray;
+    int32_t *mProximityCharsArray;
     int *mNormalizedSquaredDistances;
     int32_t mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int32_t mKeyYCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
new file mode 100644
index 0000000..1f98159
--- /dev/null
+++ b/native/jni/src/terminal_attributes.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_TERMINAL_ATTRIBUTES_H
+#define LATINIME_TERMINAL_ATTRIBUTES_H
+
+#include "unigram_dictionary.h"
+
+namespace latinime {
+
+/**
+ * This class encapsulates information about a terminal that allows to
+ * retrieve local node attributes like the list of shortcuts without
+ * exposing the format structure to the client.
+ */
+class TerminalAttributes {
+ public:
+    class ShortcutIterator {
+        const uint8_t* const mDict;
+        bool mHasNextShortcutTarget;
+        int mPos;
+
+     public:
+        ShortcutIterator(const uint8_t* dict, const int pos, const uint8_t flags) : mDict(dict),
+                mPos(pos) {
+            mHasNextShortcutTarget = (0 != (flags & UnigramDictionary::FLAG_HAS_SHORTCUT_TARGETS));
+        }
+
+        inline bool hasNextShortcutTarget() const {
+            return mHasNextShortcutTarget;
+        }
+
+        // Gets the shortcut target itself as a uint16_t string. For parameters and return value
+        // see BinaryFormat::getWordAtAddress.
+        inline int getNextShortcutTarget(const int maxDepth, uint16_t* outWord) {
+            const int shortcutFlags = BinaryFormat::getFlagsAndForwardPointer(mDict, &mPos);
+            mHasNextShortcutTarget =
+                    0 != (shortcutFlags & UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT);
+            int shortcutAddress =
+                    BinaryFormat::getAttributeAddressAndForwardPointer(mDict, shortcutFlags, &mPos);
+            return BinaryFormat::getWordAtAddress(mDict, shortcutAddress, maxDepth, outWord);
+        }
+    };
+
+ private:
+    const uint8_t* const mDict;
+    const uint8_t mFlags;
+    const int mStartPos;
+
+ public:
+    TerminalAttributes(const uint8_t* const dict, const uint8_t flags, const int pos) :
+            mDict(dict), mFlags(flags), mStartPos(pos) {
+    }
+
+    inline bool isShortcutOnly() const {
+        return 0 != (mFlags & UnigramDictionary::FLAG_IS_SHORTCUT_ONLY);
+    }
+
+    inline ShortcutIterator getShortcutIterator() const {
+        return ShortcutIterator(mDict, mStartPos, mFlags);
+    }
+};
+} // namespace latinime
+
+#endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
new file mode 100644
index 0000000..ed4c066
--- /dev/null
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -0,0 +1,894 @@
+/*
+**
+** 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.
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#define LOG_TAG "LatinIME: unigram_dictionary.cpp"
+
+#include "char_utils.h"
+#include "dictionary.h"
+#include "unigram_dictionary.h"
+
+#include "binary_format.h"
+#include "terminal_attributes.h"
+
+namespace latinime {
+
+const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] =
+        { { 'a', 'e', 0x00E4 }, // U+00E4 : LATIN SMALL LETTER A WITH DIAERESIS
+        { 'o', 'e', 0x00F6 }, // U+00F6 : LATIN SMALL LETTER O WITH DIAERESIS
+        { 'u', 'e', 0x00FC } }; // U+00FC : LATIN SMALL LETTER U WITH DIAERESIS
+
+const UnigramDictionary::digraph_t UnigramDictionary::FRENCH_LIGATURES_DIGRAPHS[] =
+        { { 'a', 'e', 0x00E6 }, // U+00E6 : LATIN SMALL LETTER AE
+        { 'o', 'e', 0x0153 } }; // U+0153 : LATIN SMALL LIGATURE OE
+
+// TODO: check the header
+UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultiplier,
+        int fullWordMultiplier, int maxWordLength, int maxWords,
+        const bool isLatestDictVersion)
+    : DICT_ROOT(streamStart), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords),
+    IS_LATEST_DICT_VERSION(isLatestDictVersion),
+    TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
+      // TODO : remove this variable.
+    ROOT_POS(0),
+    BYTES_IN_ONE_CHAR(sizeof(int)),
+    MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH) {
+    if (DEBUG_DICT) {
+        AKLOGI("UnigramDictionary - constructor");
+    }
+}
+
+UnigramDictionary::~UnigramDictionary() {
+}
+
+static inline unsigned int getCodesBufferSize(const int *codes, const int codesSize) {
+    return sizeof(*codes) * codesSize;
+}
+
+// TODO: This needs to take a const unsigned short* and not tinker with its contents
+static inline void addWord(
+        unsigned short *word, int length, int frequency, WordsPriorityQueue *queue) {
+    queue->push(frequency, word, length);
+}
+
+// Return the replacement code point for a digraph, or 0 if none.
+int UnigramDictionary::getDigraphReplacement(const int *codes, const int i, const int codesSize,
+        const digraph_t* const digraphs, const unsigned int digraphsSize) const {
+
+    // There can't be a digraph if we don't have at least 2 characters to examine
+    if (i + 2 > codesSize) return false;
+
+    // Search for the first char of some digraph
+    int lastDigraphIndex = -1;
+    const int thisChar = codes[i];
+    for (lastDigraphIndex = digraphsSize - 1; lastDigraphIndex >= 0; --lastDigraphIndex) {
+        if (thisChar == digraphs[lastDigraphIndex].first) break;
+    }
+    // No match: return early
+    if (lastDigraphIndex < 0) return 0;
+
+    // It's an interesting digraph if the second char matches too.
+    if (digraphs[lastDigraphIndex].second == codes[i + 1]) {
+        return digraphs[lastDigraphIndex].replacement;
+    } else {
+        return 0;
+    }
+}
+
+// Mostly the same arguments as the non-recursive version, except:
+// codes is the original value. It points to the start of the work buffer, and gets passed as is.
+// codesSize is the size of the user input (thus, it is the size of codesSrc).
+// codesDest is the current point in the work buffer.
+// codesSrc is the current point in the user-input, original, content-unmodified buffer.
+// codesRemain is the remaining size in codesSrc.
+void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codesBuffer,
+        int *xCoordinatesBuffer, int *yCoordinatesBuffer,
+        const int codesBufferSize, const int flags, const int *codesSrc,
+        const int codesRemain, const int currentDepth, int *codesDest, Correction *correction,
+        WordsPriorityQueuePool *queuePool,
+        const digraph_t* const digraphs, const unsigned int digraphsSize) {
+
+    const int startIndex = codesDest - codesBuffer;
+    if (currentDepth < MAX_DIGRAPH_SEARCH_DEPTH) {
+        for (int i = 0; i < codesRemain; ++i) {
+            xCoordinatesBuffer[startIndex + i] = xcoordinates[codesBufferSize - codesRemain + i];
+            yCoordinatesBuffer[startIndex + i] = ycoordinates[codesBufferSize - codesRemain + i];
+            const int replacementCodePoint =
+                    getDigraphReplacement(codesSrc, i, codesRemain, digraphs, digraphsSize);
+            if (0 != replacementCodePoint) {
+                // Found a digraph. We will try both spellings. eg. the word is "pruefen"
+
+                // Copy the word up to the first char of the digraph, including proximity chars,
+                // and overwrite the primary code with the replacement code point. Then, continue
+                // processing on the remaining part of the word, skipping the second char of the
+                // digraph.
+                // In our example, copy "pru", replace "u" with the version with the diaeresis and
+                // continue running on "fen".
+                // Make i the index of the second char of the digraph for simplicity. Forgetting
+                // to do that results in an infinite recursion so take care!
+                ++i;
+                memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
+                codesDest[(i - 1) * (BYTES_IN_ONE_CHAR / sizeof(codesDest[0]))] =
+                        replacementCodePoint;
+                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
+                        codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
+                        codesSrc + i + 1, codesRemain - i - 1,
+                        currentDepth + 1, codesDest + i, correction,
+                        queuePool, digraphs, digraphsSize);
+
+                // Copy the second char of the digraph in place, then continue processing on
+                // the remaining part of the word.
+                // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
+                memcpy(codesDest + i, codesSrc + i, BYTES_IN_ONE_CHAR);
+                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
+                        codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
+                        codesSrc + i, codesRemain - i, currentDepth + 1,
+                        codesDest + i, correction, queuePool,
+                        digraphs, digraphsSize);
+                return;
+            }
+        }
+    }
+
+    // If we come here, we hit the end of the word: let's check it against the dictionary.
+    // In our example, we'll come here once for "prufen" and then once for "pruefen".
+    // If the word contains several digraphs, we'll come it for the product of them.
+    // eg. if the word is "ueberpruefen" we'll test, in order, against
+    // "uberprufen", "uberpruefen", "ueberprufen", "ueberpruefen".
+    const unsigned int remainingBytes = BYTES_IN_ONE_CHAR * codesRemain;
+    if (0 != remainingBytes) {
+        memcpy(codesDest, codesSrc, remainingBytes);
+        memcpy(&xCoordinatesBuffer[startIndex], &xcoordinates[codesBufferSize - codesRemain],
+                sizeof(int) * codesRemain);
+        memcpy(&yCoordinatesBuffer[startIndex], &ycoordinates[codesBufferSize - codesRemain],
+                sizeof(int) * codesRemain);
+    }
+
+    getWordSuggestions(proximityInfo, xCoordinatesBuffer, yCoordinatesBuffer, codesBuffer,
+            startIndex + codesRemain, flags, correction,
+            queuePool);
+}
+
+int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo,
+        WordsPriorityQueuePool *queuePool, Correction *correction, const int *xcoordinates,
+        const int *ycoordinates, const int *codes, const int codesSize, const int flags,
+        unsigned short *outWords, int *frequencies) {
+
+    queuePool->clearAll();
+    Correction* masterCorrection = correction;
+    if (REQUIRES_GERMAN_UMLAUT_PROCESSING & flags)
+    { // Incrementally tune the word and try all possibilities
+        int codesBuffer[getCodesBufferSize(codes, codesSize)];
+        int xCoordinatesBuffer[codesSize];
+        int yCoordinatesBuffer[codesSize];
+        getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
+                xCoordinatesBuffer, yCoordinatesBuffer,
+                codesSize, flags, codes, codesSize, 0, codesBuffer, masterCorrection, queuePool,
+                GERMAN_UMLAUT_DIGRAPHS,
+                sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]));
+    } else if (REQUIRES_FRENCH_LIGATURES_PROCESSING & flags) {
+        int codesBuffer[getCodesBufferSize(codes, codesSize)];
+        int xCoordinatesBuffer[codesSize];
+        int yCoordinatesBuffer[codesSize];
+        getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
+                xCoordinatesBuffer, yCoordinatesBuffer,
+                codesSize, flags, codes, codesSize, 0, codesBuffer, masterCorrection, queuePool,
+                FRENCH_LIGATURES_DIGRAPHS,
+                sizeof(FRENCH_LIGATURES_DIGRAPHS) / sizeof(FRENCH_LIGATURES_DIGRAPHS[0]));
+    } else { // Normal processing
+        getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize, flags,
+                masterCorrection, queuePool);
+    }
+
+    PROF_START(20);
+    if (DEBUG_DICT) {
+        double ns = queuePool->getMasterQueue()->getHighestNormalizedScore(
+                proximityInfo->getPrimaryInputWord(), codesSize, 0, 0, 0);
+        ns += 0;
+        AKLOGI("Max normalized score = %f", ns);
+    }
+    const int suggestedWordsCount =
+            queuePool->getMasterQueue()->outputSuggestions(frequencies, outWords);
+
+    if (DEBUG_DICT) {
+        double ns = queuePool->getMasterQueue()->getHighestNormalizedScore(
+                proximityInfo->getPrimaryInputWord(), codesSize, 0, 0, 0);
+        ns += 0;
+        AKLOGI("Returning %d words", suggestedWordsCount);
+        /// Print the returned words
+        for (int j = 0; j < suggestedWordsCount; ++j) {
+            short unsigned int* w = outWords + j * MAX_WORD_LENGTH;
+            char s[MAX_WORD_LENGTH];
+            for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i];
+            AKLOGI("%s %i", s, frequencies[j]);
+        }
+    }
+    PROF_END(20);
+    PROF_CLOSE;
+    return suggestedWordsCount;
+}
+
+void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const int inputLength, const int flags, Correction *correction,
+        WordsPriorityQueuePool *queuePool) {
+
+    PROF_OPEN;
+    PROF_START(0);
+    PROF_END(0);
+
+    PROF_START(1);
+    const bool useFullEditDistance = USE_FULL_EDIT_DISTANCE & flags;
+    getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, useFullEditDistance,
+            inputLength, correction, queuePool);
+    PROF_END(1);
+
+    PROF_START(2);
+    // Note: This line is intentionally left blank
+    PROF_END(2);
+
+    PROF_START(3);
+    // Note: This line is intentionally left blank
+    PROF_END(3);
+
+    PROF_START(4);
+    bool hasAutoCorrectionCandidate = false;
+    WordsPriorityQueue* masterQueue = queuePool->getMasterQueue();
+    if (masterQueue->size() > 0) {
+        double nsForMaster = masterQueue->getHighestNormalizedScore(
+                proximityInfo->getPrimaryInputWord(), inputLength, 0, 0, 0);
+        hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD);
+    }
+    PROF_END(4);
+
+    PROF_START(5);
+    // Multiple word suggestions
+    if (SUGGEST_MULTIPLE_WORDS
+            && inputLength >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
+        getSplitMultipleWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
+                useFullEditDistance, inputLength, correction, queuePool,
+                hasAutoCorrectionCandidate);
+    }
+    PROF_END(5);
+
+    PROF_START(6);
+    // Note: This line is intentionally left blank
+    PROF_END(6);
+
+    if (DEBUG_DICT) {
+        queuePool->dumpSubQueue1TopSuggestions();
+        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+            WordsPriorityQueue* queue = queuePool->getSubQueue(FIRST_WORD_INDEX, i);
+            if (queue->size() > 0) {
+                WordsPriorityQueue::SuggestedWord* sw = queue->top();
+                const int score = sw->mScore;
+                const unsigned short* word = sw->mWord;
+                const int wordLength = sw->mWordLength;
+                double ns = Correction::RankingAlgorithm::calcNormalizedScore(
+                        proximityInfo->getPrimaryInputWord(), i, word, wordLength, score);
+                ns += 0;
+                AKLOGI("--- TOP SUB WORDS for %d --- %d %f [%d]", i, score, ns,
+                        (ns > TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD));
+                DUMP_WORD(proximityInfo->getPrimaryInputWord(), i);
+                DUMP_WORD(word, wordLength);
+            }
+        }
+    }
+}
+
+void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
+        const int *yCoordinates, const int *codes, const int inputLength, Correction *correction) {
+    if (DEBUG_DICT) {
+        AKLOGI("initSuggest");
+    }
+    proximityInfo->setInputParams(codes, inputLength, xCoordinates, yCoordinates);
+    const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
+    correction->initCorrection(proximityInfo, inputLength, maxDepth);
+}
+
+static const char QUOTE = '\'';
+static const char SPACE = ' ';
+
+void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const bool useFullEditDistance, const int inputLength, Correction *correction,
+        WordsPriorityQueuePool *queuePool) {
+    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
+    getSuggestionCandidates(useFullEditDistance, inputLength, correction, queuePool,
+            true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX);
+}
+
+void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
+        const int inputLength, Correction *correction, WordsPriorityQueuePool *queuePool,
+        const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) {
+    // TODO: Remove setCorrectionParams
+    correction->setCorrectionParams(0, 0, 0,
+            -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance,
+            doAutoCompletion, maxErrors);
+    int rootPosition = ROOT_POS;
+    // Get the number of children of root, then increment the position
+    int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
+    int outputIndex = 0;
+
+    correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0));
+
+    // Depth first search
+    while (outputIndex >= 0) {
+        if (correction->initProcessState(outputIndex)) {
+            int siblingPos = correction->getTreeSiblingPos(outputIndex);
+            int firstChildPos;
+
+            const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
+                    correction, &childCount, &firstChildPos, &siblingPos, queuePool,
+                    currentWordIndex);
+            // Update next sibling pos
+            correction->setTreeSiblingPos(outputIndex, siblingPos);
+
+            if (needsToTraverseChildrenNodes) {
+                // Goes to child node
+                outputIndex = correction->goDownTree(outputIndex, childCount, firstChildPos);
+            }
+        } else {
+            // Goes to parent sibling node
+            outputIndex = correction->getTreeParentIndex(outputIndex);
+        }
+    }
+}
+
+inline void UnigramDictionary::onTerminal(const int freq,
+        const TerminalAttributes& terminalAttributes, Correction *correction,
+        WordsPriorityQueuePool *queuePool, const bool addToMasterQueue,
+        const int currentWordIndex) {
+    const int inputIndex = correction->getInputIndex();
+    const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT;
+
+    int wordLength;
+    unsigned short* wordPointer;
+
+    if ((currentWordIndex == FIRST_WORD_INDEX) && addToMasterQueue) {
+        WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
+        const int finalFreq = correction->getFinalFreq(freq, &wordPointer, &wordLength);
+        if (finalFreq != NOT_A_FREQUENCY) {
+            if (!terminalAttributes.isShortcutOnly()) {
+                addWord(wordPointer, wordLength, finalFreq, masterQueue);
+            }
+
+            // Please note that the shortcut candidates will be added to the master queue only.
+            TerminalAttributes::ShortcutIterator iterator =
+                    terminalAttributes.getShortcutIterator();
+            while (iterator.hasNextShortcutTarget()) {
+                // TODO: addWord only supports weak ordering, meaning we have no means
+                // to control the order of the shortcuts relative to one another or to the word.
+                // We need to either modulate the frequency of each shortcut according
+                // to its own shortcut frequency or to make the queue
+                // so that the insert order is protected inside the queue for words
+                // with the same score.
+                uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
+                const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
+                        MAX_WORD_LENGTH_INTERNAL, shortcutTarget);
+                addWord(shortcutTarget, shortcutTargetStringLength, finalFreq, masterQueue);
+            }
+        }
+    }
+
+    // We only allow two words + other error correction for words with SUB_QUEUE_MIN_WORD_LENGTH
+    // or more length.
+    if (inputIndex >= SUB_QUEUE_MIN_WORD_LENGTH && addToSubQueue) {
+        WordsPriorityQueue *subQueue;
+        subQueue = queuePool->getSubQueue(currentWordIndex, inputIndex);
+        if (!subQueue) {
+            return;
+        }
+        const int finalFreq = correction->getFinalFreqForSubQueue(freq, &wordPointer, &wordLength,
+                inputIndex);
+        addWord(wordPointer, wordLength, finalFreq, subQueue);
+    }
+}
+
+bool UnigramDictionary::getSubStringSuggestion(
+        ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
+        const int *codes, const bool useFullEditDistance, Correction *correction,
+        WordsPriorityQueuePool* queuePool, const int inputLength,
+        const bool hasAutoCorrectionCandidate, const int currentWordIndex,
+        const int inputWordStartPos, const int inputWordLength,
+        const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
+        int*wordLengthArray, unsigned short* outputWord, int *outputWordLength) {
+    unsigned short* tempOutputWord = 0;
+    int nextWordLength = 0;
+    // TODO: Optimize init suggestion
+    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
+            inputLength, correction);
+
+    int freq = getMostFrequentWordLike(
+            inputWordStartPos, inputWordLength, proximityInfo, mWord);
+    if (freq > 0) {
+        nextWordLength = inputWordLength;
+        tempOutputWord = mWord;
+    } else if (!hasAutoCorrectionCandidate) {
+        if (inputWordStartPos > 0) {
+            const int offset = inputWordStartPos;
+            initSuggestions(proximityInfo, &xcoordinates[offset], &ycoordinates[offset],
+                    codes + offset, inputWordLength, correction);
+            queuePool->clearSubQueue(currentWordIndex);
+            getSuggestionCandidates(useFullEditDistance, inputWordLength, correction,
+                    queuePool, false, MAX_ERRORS_FOR_TWO_WORDS, currentWordIndex);
+            if (DEBUG_DICT) {
+                if (currentWordIndex < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS) {
+                    AKLOGI("Dump word candidates(%d) %d", currentWordIndex, inputWordLength);
+                    for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+                        queuePool->getSubQueue(currentWordIndex, i)->dumpTopWord();
+                    }
+                }
+            }
+        }
+        WordsPriorityQueue* queue = queuePool->getSubQueue(currentWordIndex, inputWordLength);
+        if (!queue || queue->size() < 1) {
+            return false;
+        }
+        int score = 0;
+        const double ns = queue->getHighestNormalizedScore(
+                proximityInfo->getPrimaryInputWord(), inputWordLength,
+                &tempOutputWord, &score, &nextWordLength);
+        if (DEBUG_DICT) {
+            AKLOGI("NS(%d) = %f, Score = %d", currentWordIndex, ns, score);
+        }
+        // Two words correction won't be done if the score of the first word doesn't exceed the
+        // threshold.
+        if (ns < TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD
+                || nextWordLength < SUB_QUEUE_MIN_WORD_LENGTH) {
+            return false;
+        }
+        freq = score >> (nextWordLength + TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER);
+    }
+    if (DEBUG_DICT) {
+        AKLOGI("Freq(%d): %d, length: %d, input length: %d, input start: %d (%d)"
+                , currentWordIndex, freq, nextWordLength, inputWordLength, inputWordStartPos,
+                wordLengthArray[0]);
+    }
+    if (freq <= 0 || nextWordLength <= 0
+            || MAX_WORD_LENGTH <= (outputWordStartPos + nextWordLength)) {
+        return false;
+    }
+    for (int i = 0; i < nextWordLength; ++i) {
+        outputWord[outputWordStartPos + i] = tempOutputWord[i];
+    }
+
+    // Put output values
+    freqArray[currentWordIndex] = freq;
+    // TODO: put output length instead of input length
+    wordLengthArray[currentWordIndex] = inputWordLength;
+    const int tempOutputWordLength = outputWordStartPos + nextWordLength;
+    if (outputWordLength) {
+        *outputWordLength = tempOutputWordLength;
+    }
+
+    if ((inputWordStartPos + inputWordLength) < inputLength) {
+        if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) {
+            return false;
+        }
+        outputWord[tempOutputWordLength] = SPACE;
+        if (outputWordLength) {
+            ++*outputWordLength;
+        }
+    } else if (currentWordIndex >= 1) {
+        // TODO: Handle 3 or more words
+        const int pairFreq = correction->getFreqForSplitMultipleWords(
+                freqArray, wordLengthArray, currentWordIndex + 1, isSpaceProximity, outputWord);
+        if (DEBUG_DICT) {
+            DUMP_WORD(outputWord, tempOutputWordLength);
+            AKLOGI("Split two words: %d, %d, %d, %d, (%d) %d", freqArray[0], freqArray[1], pairFreq,
+                    inputLength, wordLengthArray[0], tempOutputWordLength);
+        }
+        addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue());
+    }
+    return true;
+}
+
+void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const bool useFullEditDistance, const int inputLength,
+        Correction *correction, WordsPriorityQueuePool* queuePool,
+        const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex,
+        const int outputWordLength, int *freqArray, int* wordLengthArray,
+        unsigned short* outputWord) {
+    if (startWordIndex >= (MULTIPLE_WORDS_SUGGESTION_MAX_WORDS - 1)) {
+        // Return if the last word index
+        return;
+    }
+    if (startWordIndex >= 1
+            && (hasAutoCorrectionCandidate
+                    || inputLength < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
+        // Do not suggest 3+ words if already has auto correction candidate
+        return;
+    }
+    for (int i = startInputPos + 1; i < inputLength; ++i) {
+        if (DEBUG_CORRECTION_FREQ) {
+            AKLOGI("Multi words(%d), start in %d sep %d start out %d",
+                    startWordIndex, startInputPos, i, outputWordLength);
+            DUMP_WORD(outputWord, outputWordLength);
+        }
+        int tempOutputWordLength = 0;
+        // Current word
+        int inputWordStartPos = startInputPos;
+        int inputWordLength = i - startInputPos;
+        if (!getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
+                useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+                startWordIndex, inputWordStartPos, inputWordLength, outputWordLength,
+                true /* not used */, freqArray, wordLengthArray, outputWord,
+                &tempOutputWordLength)) {
+            continue;
+        }
+
+        if (DEBUG_CORRECTION_FREQ) {
+            AKLOGI("Do missing space correction");
+        }
+        // Next word
+        // Missing space
+        inputWordStartPos = i;
+        inputWordLength = inputLength - i;
+        if(!getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
+                useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+                startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
+                false /* missing space */, freqArray, wordLengthArray, outputWord, 0)) {
+            getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
+                    useFullEditDistance, inputLength, correction, queuePool,
+                    hasAutoCorrectionCandidate, inputWordStartPos, startWordIndex + 1,
+                    tempOutputWordLength, freqArray, wordLengthArray, outputWord);
+        }
+
+        // Mistyped space
+        ++inputWordStartPos;
+        --inputWordLength;
+
+        if (inputWordLength <= 0) {
+            continue;
+        }
+
+        const int x = xcoordinates[inputWordStartPos - 1];
+        const int y = ycoordinates[inputWordStartPos - 1];
+        if (!proximityInfo->hasSpaceProximity(x, y)) {
+            continue;
+        }
+
+        if (DEBUG_CORRECTION_FREQ) {
+            AKLOGI("Do mistyped space correction");
+        }
+        getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
+                useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+                startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
+                true /* mistyped space */, freqArray, wordLengthArray, outputWord, 0);
+    }
+}
+
+void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const bool useFullEditDistance, const int inputLength,
+        Correction *correction, WordsPriorityQueuePool* queuePool,
+        const bool hasAutoCorrectionCandidate) {
+    if (inputLength >= MAX_WORD_LENGTH) return;
+    if (DEBUG_DICT) {
+        AKLOGI("--- Suggest multiple words");
+    }
+
+    // Allocating fixed length array on stack
+    unsigned short outputWord[MAX_WORD_LENGTH];
+    int freqArray[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
+    int wordLengthArray[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
+    const int outputWordLength = 0;
+    const int startInputPos = 0;
+    const int startWordIndex = 0;
+    getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
+            useFullEditDistance, inputLength, correction, queuePool, hasAutoCorrectionCandidate,
+            startInputPos, startWordIndex, outputWordLength, freqArray, wordLengthArray,
+            outputWord);
+}
+
+// Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
+// interface.
+inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
+        const int inputLength, ProximityInfo *proximityInfo, unsigned short *word) {
+    uint16_t inWord[inputLength];
+
+    for (int i = 0; i < inputLength; ++i) {
+        inWord[i] = (uint16_t)proximityInfo->getPrimaryCharAt(startInputIndex + i);
+    }
+    return getMostFrequentWordLikeInner(inWord, inputLength, word);
+}
+
+// This function will take the position of a character array within a CharGroup,
+// and check it actually like-matches the word in inWord starting at startInputIndex,
+// that is, it matches it with case and accents squashed.
+// The function returns true if there was a full match, false otherwise.
+// The function will copy on-the-fly the characters in the CharGroup to outNewWord.
+// It will also place the end position of the array in outPos; in outInputIndex,
+// it will place the index of the first char AFTER the match if there was a match,
+// and the initial position if there was not. It makes sense because if there was
+// a match we want to continue searching, but if there was not, we want to go to
+// the next CharGroup.
+// In and out parameters may point to the same location. This function takes care
+// not to use any input parameters after it wrote into its outputs.
+static inline bool testCharGroupForContinuedLikeness(const uint8_t flags,
+        const uint8_t* const root, const int startPos,
+        const uint16_t* const inWord, const int startInputIndex,
+        int32_t* outNewWord, int* outInputIndex, int* outPos) {
+    const bool hasMultipleChars = (0 != (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags));
+    int pos = startPos;
+    int32_t character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
+    int32_t baseChar = toBaseLowerCase(character);
+    const uint16_t wChar = toBaseLowerCase(inWord[startInputIndex]);
+
+    if (baseChar != wChar) {
+        *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos;
+        *outInputIndex = startInputIndex;
+        return false;
+    }
+    int inputIndex = startInputIndex;
+    outNewWord[inputIndex] = character;
+    if (hasMultipleChars) {
+        character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
+        while (NOT_A_CHARACTER != character) {
+            baseChar = toBaseLowerCase(character);
+            if (toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
+                *outPos = BinaryFormat::skipOtherCharacters(root, pos);
+                *outInputIndex = startInputIndex;
+                return false;
+            }
+            outNewWord[inputIndex] = character;
+            character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
+        }
+    }
+    *outInputIndex = inputIndex + 1;
+    *outPos = pos;
+    return true;
+}
+
+// This function is invoked when a word like the word searched for is found.
+// It will compare the frequency to the max frequency, and if greater, will
+// copy the word into the output buffer. In output value maxFreq, it will
+// write the new maximum frequency if it changed.
+static inline void onTerminalWordLike(const int freq, int32_t* newWord, const int length,
+        short unsigned int* outWord, int* maxFreq) {
+    if (freq > *maxFreq) {
+        for (int q = 0; q < length; ++q)
+            outWord[q] = newWord[q];
+        outWord[length] = 0;
+        *maxFreq = freq;
+    }
+}
+
+// Will find the highest frequency of the words like the one passed as an argument,
+// that is, everything that only differs by case/accents.
+int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t * const inWord,
+        const int length, short unsigned int* outWord) {
+    int32_t newWord[MAX_WORD_LENGTH_INTERNAL];
+    int depth = 0;
+    int maxFreq = -1;
+    const uint8_t* const root = DICT_ROOT;
+
+    int startPos = 0;
+    mStackChildCount[0] = BinaryFormat::getGroupCountAndForwardPointer(root, &startPos);
+    mStackInputIndex[0] = 0;
+    mStackSiblingPos[0] = startPos;
+    while (depth >= 0) {
+        const int charGroupCount = mStackChildCount[depth];
+        int pos = mStackSiblingPos[depth];
+        for (int charGroupIndex = charGroupCount - 1; charGroupIndex >= 0; --charGroupIndex) {
+            int inputIndex = mStackInputIndex[depth];
+            const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
+            // Test whether all chars in this group match with the word we are searching for. If so,
+            // we want to traverse its children (or if the length match, evaluate its frequency).
+            // Note that this function will output the position regardless, but will only write
+            // into inputIndex if there is a match.
+            const bool isAlike = testCharGroupForContinuedLikeness(flags, root, pos, inWord,
+                    inputIndex, newWord, &inputIndex, &pos);
+            if (isAlike && (FLAG_IS_TERMINAL & flags) && (inputIndex == length)) {
+                const int frequency = BinaryFormat::readFrequencyWithoutMovingPointer(root, pos);
+                onTerminalWordLike(frequency, newWord, inputIndex, outWord, &maxFreq);
+            }
+            pos = BinaryFormat::skipFrequency(flags, pos);
+            const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
+            const int childrenNodePos = BinaryFormat::readChildrenPosition(root, flags, pos);
+            // If we had a match and the word has children, we want to traverse them. We don't have
+            // to traverse words longer than the one we are searching for, since they will not match
+            // anyway, so don't traverse unless inputIndex < length.
+            if (isAlike && (-1 != childrenNodePos) && (inputIndex < length)) {
+                // Save position for this depth, to get back to this once children are done
+                mStackChildCount[depth] = charGroupIndex;
+                mStackSiblingPos[depth] = siblingPos;
+                // Prepare stack values for next depth
+                ++depth;
+                int childrenPos = childrenNodePos;
+                mStackChildCount[depth] =
+                        BinaryFormat::getGroupCountAndForwardPointer(root, &childrenPos);
+                mStackSiblingPos[depth] = childrenPos;
+                mStackInputIndex[depth] = inputIndex;
+                pos = childrenPos;
+                // Go to the next depth level.
+                ++depth;
+                break;
+            } else {
+                // No match, or no children, or word too long to ever match: go the next sibling.
+                pos = siblingPos;
+            }
+        }
+        --depth;
+    }
+    return maxFreq;
+}
+
+bool UnigramDictionary::isValidWord(const uint16_t* const inWord, const int length) const {
+    return NOT_VALID_WORD != BinaryFormat::getTerminalPosition(DICT_ROOT, inWord, length);
+}
+
+// TODO: remove this function.
+int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offset,
+        int length) const {
+    return -1;
+}
+
+// ProcessCurrentNode returns a boolean telling whether to traverse children nodes or not.
+// If the return value is false, then the caller should read in the output "nextSiblingPosition"
+// to find out the address of the next sibling node and pass it to a new call of processCurrentNode.
+// It is worthy to note that when false is returned, the output values other than
+// nextSiblingPosition are undefined.
+// If the return value is true, then the caller must proceed to traverse the children of this
+// node. processCurrentNode will output the information about the children: their count in
+// newCount, their position in newChildrenPosition, the traverseAllNodes flag in
+// newTraverseAllNodes, the match weight into newMatchRate, the input index into newInputIndex, the
+// diffs into newDiffs, the sibling position in nextSiblingPosition, and the output index into
+// newOutputIndex. Please also note the following caveat: processCurrentNode does not know when
+// there aren't any more nodes at this level, it merely returns the address of the first byte after
+// the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any
+// given level, as output into newCount when traversing this level's parent.
+inline bool UnigramDictionary::processCurrentNode(const int initialPos,
+        Correction *correction, int *newCount,
+        int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
+        const int currentWordIndex) {
+    if (DEBUG_DICT) {
+        correction->checkState();
+    }
+    int pos = initialPos;
+
+    // Flags contain the following information:
+    // - Address type (MASK_GROUP_ADDRESS_TYPE) on two bits:
+    //   - FLAG_GROUP_ADDRESS_TYPE_{ONE,TWO,THREE}_BYTES means there are children and their address
+    //     is on the specified number of bytes.
+    //   - FLAG_GROUP_ADDRESS_TYPE_NOADDRESS means there are no children, and therefore no address.
+    // - FLAG_HAS_MULTIPLE_CHARS: whether this node has multiple char or not.
+    // - FLAG_IS_TERMINAL: whether this node is a terminal or not (it may still have children)
+    // - FLAG_HAS_BIGRAMS: whether this node has bigrams or not
+    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(DICT_ROOT, &pos);
+    const bool hasMultipleChars = (0 != (FLAG_HAS_MULTIPLE_CHARS & flags));
+    const bool isTerminalNode = (0 != (FLAG_IS_TERMINAL & flags));
+
+    bool needsToInvokeOnTerminal = false;
+
+    // This gets only ONE character from the stream. Next there will be:
+    // if FLAG_HAS_MULTIPLE CHARS: the other characters of the same node
+    // else if FLAG_IS_TERMINAL: the frequency
+    // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address
+    // Note that you can't have a node that both is not a terminal and has no children.
+    int32_t c = BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos);
+    assert(NOT_A_CHARACTER != c);
+
+    // We are going to loop through each character and make it look like it's a different
+    // node each time. To do that, we will process characters in this node in order until
+    // we find the character terminator. This is signalled by getCharCode* returning
+    // NOT_A_CHARACTER.
+    // As a special case, if there is only one character in this node, we must not read the
+    // next bytes so we will simulate the NOT_A_CHARACTER return by testing the flags.
+    // This way, each loop run will look like a "virtual node".
+    do {
+        // We prefetch the next char. If 'c' is the last char of this node, we will have
+        // NOT_A_CHARACTER in the next char. From this we can decide whether this virtual node
+        // should behave as a terminal or not and whether we have children.
+        const int32_t nextc = hasMultipleChars
+                ? BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos) : NOT_A_CHARACTER;
+        const bool isLastChar = (NOT_A_CHARACTER == nextc);
+        // If there are more chars in this nodes, then this virtual node is not a terminal.
+        // If we are on the last char, this virtual node is a terminal if this node is.
+        const bool isTerminal = isLastChar && isTerminalNode;
+
+        Correction::CorrectionType stateType = correction->processCharAndCalcState(
+                c, isTerminal);
+        if (stateType == Correction::TRAVERSE_ALL_ON_TERMINAL
+                || stateType == Correction::ON_TERMINAL) {
+            needsToInvokeOnTerminal = true;
+        } else if (stateType == Correction::UNRELATED || correction->needsToPrune()) {
+            // We found that this is an unrelated character, so we should give up traversing
+            // this node and its children entirely.
+            // However we may not be on the last virtual node yet so we skip the remaining
+            // characters in this node, the frequency if it's there, read the next sibling
+            // position to output it, then return false.
+            // We don't have to output other values because we return false, as in
+            // "don't traverse children".
+            if (!isLastChar) {
+                pos = BinaryFormat::skipOtherCharacters(DICT_ROOT, pos);
+            }
+            pos = BinaryFormat::skipFrequency(flags, pos);
+            *nextSiblingPosition =
+                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
+            return false;
+        }
+
+        // Prepare for the next character. Promote the prefetched char to current char - the loop
+        // will take care of prefetching the next. If we finally found our last char, nextc will
+        // contain NOT_A_CHARACTER.
+        c = nextc;
+    } while (NOT_A_CHARACTER != c);
+
+    if (isTerminalNode) {
+        // The frequency should be here, because we come here only if this is actually
+        // a terminal node, and we are on its last char.
+        const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
+        const int childrenAddressPos = BinaryFormat::skipFrequency(flags, pos);
+        const int attributesPos = BinaryFormat::skipChildrenPosition(flags, childrenAddressPos);
+        TerminalAttributes terminalAttributes(DICT_ROOT, flags, attributesPos);
+        onTerminal(freq, terminalAttributes, correction, queuePool, needsToInvokeOnTerminal,
+                currentWordIndex);
+
+        // If there are more chars in this node, then this virtual node has children.
+        // If we are on the last char, this virtual node has children if this node has.
+        const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
+
+        // This character matched the typed character (enough to traverse the node at least)
+        // so we just evaluated it. Now we should evaluate this virtual node's children - that
+        // is, if it has any. If it has no children, we're done here - so we skip the end of
+        // the node, output the siblings position, and return false "don't traverse children".
+        // Note that !hasChildren implies isLastChar, so we know we don't have to skip any
+        // remaining char in this group for there can't be any.
+        if (!hasChildren) {
+            pos = BinaryFormat::skipFrequency(flags, pos);
+            *nextSiblingPosition =
+                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
+            return false;
+        }
+
+        // Optimization: Prune out words that are too long compared to how much was typed.
+        if (correction->needsToPrune()) {
+            pos = BinaryFormat::skipFrequency(flags, pos);
+            *nextSiblingPosition =
+                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
+            if (DEBUG_DICT_FULL) {
+                AKLOGI("Traversing was pruned.");
+            }
+            return false;
+        }
+    }
+
+    // Now we finished processing this node, and we want to traverse children. If there are no
+    // children, we can't come here.
+    assert(BinaryFormat::hasChildrenInFlags(flags));
+
+    // If this node was a terminal it still has the frequency under the pointer (it may have been
+    // read, but not skipped - see readFrequencyWithoutMovingPointer).
+    // Next come the children position, then possibly attributes (attributes are bigrams only for
+    // now, maybe something related to shortcuts in the future).
+    // Once this is read, we still need to output the number of nodes in the immediate children of
+    // this node, so we read and output it before returning true, as in "please traverse children".
+    pos = BinaryFormat::skipFrequency(flags, pos);
+    int childrenPos = BinaryFormat::readChildrenPosition(DICT_ROOT, flags, pos);
+    *nextSiblingPosition = BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
+    *newCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &childrenPos);
+    *newChildrenPosition = childrenPos;
+    return true;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
new file mode 100644
index 0000000..c8f1556
--- /dev/null
+++ b/native/jni/src/unigram_dictionary.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_UNIGRAM_DICTIONARY_H
+#define LATINIME_UNIGRAM_DICTIONARY_H
+
+#include <stdint.h>
+#include "correction.h"
+#include "correction_state.h"
+#include "defines.h"
+#include "proximity_info.h"
+#include "words_priority_queue.h"
+#include "words_priority_queue_pool.h"
+
+namespace latinime {
+
+class TerminalAttributes;
+class UnigramDictionary {
+    typedef struct { int first; int second; int replacement; } digraph_t;
+
+ public:
+    // Mask and flags for children address type selection.
+    static const int MASK_GROUP_ADDRESS_TYPE = 0xC0;
+    static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
+    static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
+    static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
+    static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+
+    // Flag for single/multiple char group
+    static const int FLAG_HAS_MULTIPLE_CHARS = 0x20;
+
+    // Flag for terminal groups
+    static const int FLAG_IS_TERMINAL = 0x10;
+
+    // Flag for shortcut targets presence
+    static const int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
+    // Flag for bigram presence
+    static const int FLAG_HAS_BIGRAMS = 0x04;
+    // Flag for shortcut-only words. Some words are shortcut-only, which means they match when
+    // the user types them but they don't pop in the suggestion strip, only the words they are
+    // shortcuts for do.
+    static const int FLAG_IS_SHORTCUT_ONLY = 0x02;
+
+    // Attribute (bigram/shortcut) related flags:
+    // Flag for presence of more attributes
+    static const int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
+    // Flag for sign of offset. If this flag is set, the offset value must be negated.
+    static const int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
+
+    // Mask for attribute frequency, stored on 4 bits inside the flags byte.
+    static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
+
+    // Mask and flags for attribute address type selection.
+    static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
+    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
+    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
+    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
+
+    // Error tolerances
+    static const int DEFAULT_MAX_ERRORS = 2;
+    static const int MAX_ERRORS_FOR_TWO_WORDS = 1;
+
+    UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultipler,
+            int fullWordMultiplier, int maxWordLength, int maxWords,
+            const bool isLatestDictVersion);
+    bool isValidWord(const uint16_t* const inWord, const int length) const;
+    int getBigramPosition(int pos, unsigned short *word, int offset, int length) const;
+    int getSuggestions(ProximityInfo *proximityInfo, WordsPriorityQueuePool *queuePool,
+            Correction *correction, const int *xcoordinates,
+            const int *ycoordinates, const int *codes, const int codesSize, const int flags,
+            unsigned short *outWords, int *frequencies);
+    virtual ~UnigramDictionary();
+
+ private:
+    void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
+            const int *ycoordinates, const int *codes, const int inputLength,
+            const int flags, Correction *correction, WordsPriorityQueuePool *queuePool);
+    int getDigraphReplacement(const int *codes, const int i, const int codesSize,
+            const digraph_t* const digraphs, const unsigned int digraphsSize) const;
+    void getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
+        int *xCoordinatesBuffer, int *yCoordinatesBuffer,
+        const int codesBufferSize, const int flags, const int* codesSrc,
+        const int codesRemain, const int currentDepth, int* codesDest, Correction *correction,
+        WordsPriorityQueuePool* queuePool, const digraph_t* const digraphs,
+        const unsigned int digraphsSize);
+    void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
+            const int *ycoordinates, const int *codes, const int codesSize, Correction *correction);
+    void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
+            const int *ycoordinates, const int *codes, const bool useFullEditDistance,
+            const int inputLength, Correction *correction, WordsPriorityQueuePool* queuePool);
+    void getSuggestionCandidates(
+            const bool useFullEditDistance, const int inputLength, Correction *correction,
+            WordsPriorityQueuePool* queuePool, const bool doAutoCompletion, const int maxErrors,
+            const int currentWordIndex);
+    void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
+            const int *xcoordinates, const int *ycoordinates, const int *codes,
+            const bool useFullEditDistance, const int inputLength,
+            Correction *correction, WordsPriorityQueuePool* queuePool,
+            const bool hasAutoCorrectionCandidate);
+    void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
+            Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue,
+            const int currentWordIndex);
+    bool needsToSkipCurrentNode(const unsigned short c,
+            const int inputIndex, const int skipPos, const int depth);
+    // Process a node by considering proximity, missing and excessive character
+    bool processCurrentNode(const int initialPos, Correction *correction, int *newCount,
+            int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
+            const int currentWordIndex);
+    int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
+            ProximityInfo *proximityInfo, unsigned short *word);
+    int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,
+            short unsigned int *outWord);
+    bool getSubStringSuggestion(
+            ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
+            const int *codes, const bool useFullEditDistance, Correction *correction,
+            WordsPriorityQueuePool* queuePool, const int inputLength,
+            const bool hasAutoCorrectionCandidate, const int currentWordIndex,
+            const int inputWordStartPos, const int inputWordLength,
+            const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
+            int *wordLengthArray, unsigned short* outputWord, int *outputWordLength);
+    void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
+            const int *xcoordinates, const int *ycoordinates, const int *codes,
+            const bool useFullEditDistance, const int inputLength,
+            Correction *correction, WordsPriorityQueuePool* queuePool,
+            const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex,
+            const int outputWordLength, int *freqArray, int* wordLengthArray,
+            unsigned short* outputWord);
+
+    const uint8_t* const DICT_ROOT;
+    const int MAX_WORD_LENGTH;
+    const int MAX_WORDS;
+    const bool IS_LATEST_DICT_VERSION;
+    const int TYPED_LETTER_MULTIPLIER;
+    const int FULL_WORD_MULTIPLIER;
+    const int ROOT_POS;
+    const unsigned int BYTES_IN_ONE_CHAR;
+    const int MAX_DIGRAPH_SEARCH_DEPTH;
+
+    // Flags for special processing
+    // Those *must* match the flags in BinaryDictionary.Flags.ALL_FLAGS in BinaryDictionary.java
+    // or something very bad (like, the apocalypse) will happen.
+    // Please update both at the same time.
+    enum {
+        REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1,
+        USE_FULL_EDIT_DISTANCE = 0x2,
+        REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
+    };
+    static const digraph_t GERMAN_UMLAUT_DIGRAPHS[];
+    static const digraph_t FRENCH_LIGATURES_DIGRAPHS[];
+
+    // Still bundled members
+    unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
+    int mStackChildCount[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
+    int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
+    int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
+};
+} // namespace latinime
+
+#endif // LATINIME_UNIGRAM_DICTIONARY_H
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
new file mode 100644
index 0000000..249962e
--- /dev/null
+++ b/native/jni/src/words_priority_queue.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_WORDS_PRIORITY_QUEUE_H
+#define LATINIME_WORDS_PRIORITY_QUEUE_H
+
+#include <cstring> // for memcpy()
+#include <iostream>
+#include <queue>
+#include "defines.h"
+
+namespace latinime {
+
+class WordsPriorityQueue {
+ public:
+    class SuggestedWord {
+    public:
+        int mScore;
+        unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
+        int mWordLength;
+        bool mUsed;
+
+        void setParams(int score, unsigned short* word, int wordLength) {
+            mScore = score;
+            mWordLength = wordLength;
+            memcpy(mWord, word, sizeof(unsigned short) * wordLength);
+            mUsed = true;
+        }
+    };
+
+    WordsPriorityQueue(int maxWords, int maxWordLength) :
+            MAX_WORDS((unsigned int) maxWords), MAX_WORD_LENGTH(
+                    (unsigned int) maxWordLength) {
+        mSuggestedWords = new SuggestedWord[maxWordLength];
+        for (int i = 0; i < maxWordLength; ++i) {
+            mSuggestedWords[i].mUsed = false;
+        }
+        mHighestSuggestedWord = 0;
+    }
+
+    ~WordsPriorityQueue() {
+        delete[] mSuggestedWords;
+    }
+
+    void push(int score, unsigned short* word, int wordLength) {
+        SuggestedWord* sw = 0;
+        if (mSuggestions.size() >= MAX_WORDS) {
+            sw = mSuggestions.top();
+            const int minScore = sw->mScore;
+            if (minScore >= score) {
+                return;
+            } else {
+                sw->mUsed = false;
+                mSuggestions.pop();
+            }
+        }
+        if (sw == 0) {
+            sw = getFreeSuggestedWord(score, word, wordLength);
+        } else {
+            sw->setParams(score, word, wordLength);
+        }
+        if (sw == 0) {
+            AKLOGE("SuggestedWord is accidentally null.");
+            return;
+        }
+        if (DEBUG_WORDS_PRIORITY_QUEUE) {
+            AKLOGI("Push word. %d, %d", score, wordLength);
+            DUMP_WORD(word, wordLength);
+        }
+        mSuggestions.push(sw);
+        if (!mHighestSuggestedWord || mHighestSuggestedWord->mScore < sw->mScore) {
+            mHighestSuggestedWord = sw;
+        }
+    }
+
+    SuggestedWord* top() {
+        if (mSuggestions.empty()) return 0;
+        SuggestedWord* sw = mSuggestions.top();
+        return sw;
+    }
+
+    int outputSuggestions(int *frequencies, unsigned short *outputChars) {
+        mHighestSuggestedWord = 0;
+        const unsigned int size = min(
+              MAX_WORDS, static_cast<unsigned int>(mSuggestions.size()));
+        int index = size - 1;
+        while (!mSuggestions.empty() && index >= 0) {
+            SuggestedWord* sw = mSuggestions.top();
+            if (DEBUG_WORDS_PRIORITY_QUEUE) {
+                AKLOGI("dump word. %d", sw->mScore);
+                DUMP_WORD(sw->mWord, sw->mWordLength);
+            }
+            const unsigned int wordLength = sw->mWordLength;
+            char* targetAdr = (char*) outputChars
+                    + (index) * MAX_WORD_LENGTH * sizeof(short);
+            frequencies[index] = sw->mScore;
+            memcpy(targetAdr, sw->mWord, (wordLength) * sizeof(short));
+            if (wordLength < MAX_WORD_LENGTH) {
+                ((unsigned short*) targetAdr)[wordLength] = 0;
+            }
+            sw->mUsed = false;
+            mSuggestions.pop();
+            --index;
+        }
+        return size;
+    }
+
+    int size() const {
+        return mSuggestions.size();
+    }
+
+    void clear() {
+        mHighestSuggestedWord = 0;
+        while (!mSuggestions.empty()) {
+            SuggestedWord* sw = mSuggestions.top();
+            if (DEBUG_WORDS_PRIORITY_QUEUE) {
+                AKLOGI("Clear word. %d", sw->mScore);
+                DUMP_WORD(sw->mWord, sw->mWordLength);
+            }
+            sw->mUsed = false;
+            mSuggestions.pop();
+        }
+    }
+
+    void dumpTopWord() {
+        if (size() <= 0) {
+            return;
+        }
+        DUMP_WORD(mHighestSuggestedWord->mWord, mHighestSuggestedWord->mWordLength);
+    }
+
+    double getHighestNormalizedScore(const unsigned short* before, const int beforeLength,
+            unsigned short** outWord, int *outScore, int *outLength) {
+        if (!mHighestSuggestedWord) {
+            return 0.0;
+        }
+        SuggestedWord* sw = mHighestSuggestedWord;
+        const int score = sw->mScore;
+        unsigned short* word = sw->mWord;
+        const int wordLength = sw->mWordLength;
+        if (outScore) {
+            *outScore = score;
+        }
+        if (outWord) {
+            *outWord = word;
+        }
+        if (outLength) {
+            *outLength = wordLength;
+        }
+        return Correction::RankingAlgorithm::calcNormalizedScore(
+                before, beforeLength, word, wordLength, score);
+    }
+
+ private:
+    struct wordComparator {
+        bool operator ()(SuggestedWord * left, SuggestedWord * right) {
+            return left->mScore > right->mScore;
+        }
+    };
+
+    SuggestedWord* getFreeSuggestedWord(int score, unsigned short* word,
+            int wordLength) {
+        for (unsigned int i = 0; i < MAX_WORD_LENGTH; ++i) {
+            if (!mSuggestedWords[i].mUsed) {
+                mSuggestedWords[i].setParams(score, word, wordLength);
+                return &mSuggestedWords[i];
+            }
+        }
+        return 0;
+    }
+
+    typedef std::priority_queue<SuggestedWord*, std::vector<SuggestedWord*>,
+            wordComparator> Suggestions;
+    Suggestions mSuggestions;
+    const unsigned int MAX_WORDS;
+    const unsigned int MAX_WORD_LENGTH;
+    SuggestedWord* mSuggestedWords;
+    SuggestedWord* mHighestSuggestedWord;
+};
+}
+
+#endif // LATINIME_WORDS_PRIORITY_QUEUE_H
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
new file mode 100644
index 0000000..5b50e8f
--- /dev/null
+++ b/native/jni/src/words_priority_queue_pool.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
+#define LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
+
+#include <assert.h>
+#include <new>
+#include "words_priority_queue.h"
+
+namespace latinime {
+
+class WordsPriorityQueuePool {
+ public:
+    WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) {
+        mMasterQueue = new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords, maxWordLength);
+        for (int i = 0, subQueueBufOffset = 0;
+                i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT;
+                ++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) {
+            mSubQueues[i] = new(mSubQueueBuf + subQueueBufOffset)
+                    WordsPriorityQueue(subQueueMaxWords, maxWordLength);
+        }
+    }
+
+    virtual ~WordsPriorityQueuePool() {
+    }
+
+    WordsPriorityQueue* getMasterQueue() {
+        return mMasterQueue;
+    }
+
+    WordsPriorityQueue* getSubQueue(const int wordIndex, const int inputWordLength) {
+        if (wordIndex >= MULTIPLE_WORDS_SUGGESTION_MAX_WORDS) {
+            return 0;
+        }
+        if (inputWordLength < 0 || inputWordLength >= SUB_QUEUE_MAX_COUNT) {
+            if (DEBUG_WORDS_PRIORITY_QUEUE) {
+                assert(false);
+            }
+            return 0;
+        }
+        return mSubQueues[wordIndex * SUB_QUEUE_MAX_COUNT + inputWordLength];
+    }
+
+    inline void clearAll() {
+        mMasterQueue->clear();
+        for (int i = 0; i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS; ++i) {
+            clearSubQueue(i);
+        }
+    }
+
+    inline void clearSubQueue(const int wordIndex) {
+        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+            WordsPriorityQueue* queue = getSubQueue(wordIndex, i);
+            if (queue) {
+                queue->clear();
+            }
+        }
+    }
+
+    void dumpSubQueue1TopSuggestions() {
+        AKLOGI("DUMP SUBQUEUE1 TOP SUGGESTIONS");
+        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+            getSubQueue(0, i)->dumpTopWord();
+        }
+    }
+
+ private:
+    WordsPriorityQueue* mMasterQueue;
+    WordsPriorityQueue* mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
+    char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
+    char mSubQueueBuf[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
+                      * SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
+};
+}
+
+#endif // LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
diff --git a/native/src/char_utils.h b/native/src/char_utils.h
deleted file mode 100644
index a69a35e..0000000
--- a/native/src/char_utils.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_CHAR_UTILS_H
-#define LATINIME_CHAR_UTILS_H
-
-namespace latinime {
-
-unsigned short latin_tolower(unsigned short c);
-
-} // namespace latinime
-
-#endif // LATINIME_CHAR_UTILS_H
diff --git a/native/src/correction.h b/native/src/correction.h
deleted file mode 100644
index d4e99f0..0000000
--- a/native/src/correction.h
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_CORRECTION_H
-#define LATINIME_CORRECTION_H
-
-#include <stdint.h>
-#include "correction_state.h"
-
-#include "defines.h"
-
-namespace latinime {
-
-class ProximityInfo;
-
-class Correction {
-
-public:
-    typedef enum {
-        TRAVERSE_ALL_ON_TERMINAL,
-        TRAVERSE_ALL_NOT_ON_TERMINAL,
-        UNRELATED,
-        ON_TERMINAL,
-        NOT_ON_TERMINAL
-    } CorrectionType;
-
-    Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
-    void initCorrection(
-            const ProximityInfo *pi, const int inputLength, const int maxWordLength);
-    void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
-
-    // TODO: remove
-    void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
-            const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance);
-    void checkState();
-    bool initProcessState(const int index);
-
-    int getOutputIndex();
-    int getInputIndex();
-
-    virtual ~Correction();
-    int getSpaceProximityPos() const {
-        return mSpaceProximityPos;
-    }
-    int getMissingSpacePos() const {
-        return mMissingSpacePos;
-    }
-
-    int getSkipPos() const {
-        return mSkipPos;
-    }
-
-    int getExcessivePos() const {
-        return mExcessivePos;
-    }
-
-    int getTransposedPos() const {
-        return mTransposedPos;
-    }
-
-    bool needsToPrune() const;
-
-    int getFreqForSplitTwoWords(
-            const int firstFreq, const int secondFreq, const unsigned short *word);
-    int getFinalFreq(const int freq, unsigned short **word, int* wordLength);
-
-    CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
-
-    /////////////////////////
-    // Tree helper methods
-    int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
-
-    inline int getTreeSiblingPos(const int index) const {
-        return mCorrectionStates[index].mSiblingPos;
-    }
-
-    inline void setTreeSiblingPos(const int index, const int pos) {
-        mCorrectionStates[index].mSiblingPos = pos;
-    }
-
-    inline int getTreeParentIndex(const int index) const {
-        return mCorrectionStates[index].mParentIndex;
-    }
-private:
-    inline void incrementInputIndex();
-    inline void incrementOutputIndex();
-    inline bool needsToTraverseAllNodes();
-    inline void startToTraverseAllNodes();
-    inline bool isQuote(const unsigned short c);
-    inline CorrectionType processSkipChar(
-            const int32_t c, const bool isTerminal, const bool inputIndexIncremented);
-    inline void addCharToCurrentWord(const int32_t c);
-
-    const int TYPED_LETTER_MULTIPLIER;
-    const int FULL_WORD_MULTIPLIER;
-    const ProximityInfo *mProximityInfo;
-
-    bool mUseFullEditDistance;
-    int mMaxEditDistance;
-    int mMaxDepth;
-    int mInputLength;
-    int mSpaceProximityPos;
-    int mMissingSpacePos;
-    int mTerminalInputIndex;
-    int mTerminalOutputIndex;
-
-    // The following arrays are state buffer.
-    unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
-    int mDistances[MAX_WORD_LENGTH_INTERNAL];
-
-    // Edit distance calculation requires a buffer with (N+1)^2 length for the input length N.
-    // Caveat: Do not create multiple tables per thread as this table eats up RAM a lot.
-    int mEditDistanceTable[(MAX_WORD_LENGTH_INTERNAL + 1) * (MAX_WORD_LENGTH_INTERNAL + 1)];
-
-    CorrectionState mCorrectionStates[MAX_WORD_LENGTH_INTERNAL];
-
-    // The following member variables are being used as cache values of the correction state.
-    bool mNeedsToTraverseAllNodes;
-    int mOutputIndex;
-    int mInputIndex;
-
-    int mEquivalentCharCount;
-    int mProximityCount;
-    int mExcessiveCount;
-    int mTransposedCount;
-    int mSkippedCount;
-
-    int mTransposedPos;
-    int mExcessivePos;
-    int mSkipPos;
-
-    bool mLastCharExceeded;
-
-    bool mMatching;
-    bool mProximityMatching;
-    bool mExceeding;
-    bool mTransposing;
-    bool mSkipping;
-
-    class RankingAlgorithm {
-    public:
-        static int calculateFinalFreq(const int inputIndex, const int depth,
-                const int freq, int *editDistanceTable, const Correction* correction);
-        static int calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq,
-                const Correction* correction, const unsigned short *word);
-    };
-};
-} // namespace latinime
-#endif // LATINIME_CORRECTION_H
diff --git a/native/src/dictionary.h b/native/src/dictionary.h
deleted file mode 100644
index d5de008..0000000
--- a/native/src/dictionary.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      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.
- */
-
-#ifndef LATINIME_DICTIONARY_H
-#define LATINIME_DICTIONARY_H
-
-#include "basechars.h"
-#include "bigram_dictionary.h"
-#include "char_utils.h"
-#include "defines.h"
-#include "proximity_info.h"
-#include "unigram_dictionary.h"
-
-namespace latinime {
-
-class Dictionary {
-public:
-    Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler,
-            int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives);
-    int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
-            int *codes, int codesSize, int flags, unsigned short *outWords, int *frequencies) {
-        return mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
-                codesSize, flags, outWords, frequencies);
-    }
-
-    // TODO: Call mBigramDictionary instead of mUnigramDictionary
-    int getBigrams(unsigned short *word, int length, int *codes, int codesSize,
-            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams,
-            int maxAlternatives) {
-        return mBigramDictionary->getBigrams(word, length, codes, codesSize, outWords, frequencies,
-                maxWordLength, maxBigrams, maxAlternatives);
-    }
-
-    bool isValidWord(unsigned short *word, int length);
-    void *getDict() { return (void *)mDict; }
-    int getDictSize() { return mDictSize; }
-    int getMmapFd() { return mMmapFd; }
-    int getDictBufAdjust() { return mDictBufAdjust; }
-    ~Dictionary();
-
-    // public static utility methods
-    // static inline methods should be defined in the header file
-    static unsigned short getChar(const unsigned char *dict, int *pos);
-    static int getCount(const unsigned char *dict, int *pos);
-    static bool getTerminal(const unsigned char *dict, int *pos);
-    static int getAddress(const unsigned char *dict, int *pos);
-    static int getFreq(const unsigned char *dict, const bool isLatestDictVersion, int *pos);
-    static int wideStrLen(unsigned short *str);
-    // returns next sibling's position
-    static int setDictionaryValues(const unsigned char *dict, const bool isLatestDictVersion,
-            const int pos, unsigned short *c, int *childrenPosition,
-            bool *terminal, int *freq);
-    static inline unsigned short toBaseLowerCase(unsigned short c);
-
-private:
-    bool hasBigram();
-
-    const unsigned char *mDict;
-
-    // Used only for the mmap version of dictionary loading, but we use these as dummy variables
-    // also for the malloc version.
-    const int mDictSize;
-    const int mMmapFd;
-    const int mDictBufAdjust;
-
-    const bool IS_LATEST_DICT_VERSION;
-    UnigramDictionary *mUnigramDictionary;
-    BigramDictionary *mBigramDictionary;
-};
-
-// public static utility methods
-// static inline methods should be defined in the header file
-inline unsigned short Dictionary::getChar(const unsigned char *dict, int *pos) {
-    unsigned short ch = (unsigned short) (dict[(*pos)++] & 0xFF);
-    // If the code is 255, then actual 16 bit code follows (in big endian)
-    if (ch == 0xFF) {
-        ch = ((dict[*pos] & 0xFF) << 8) | (dict[*pos + 1] & 0xFF);
-        (*pos) += 2;
-    }
-    return ch;
-}
-
-inline int Dictionary::getCount(const unsigned char *dict, int *pos) {
-    return dict[(*pos)++] & 0xFF;
-}
-
-inline bool Dictionary::getTerminal(const unsigned char *dict, int *pos) {
-    return (dict[*pos] & FLAG_TERMINAL_MASK) > 0;
-}
-
-inline int Dictionary::getAddress(const unsigned char *dict, int *pos) {
-    int address = 0;
-    if ((dict[*pos] & FLAG_ADDRESS_MASK) == 0) {
-        *pos += 1;
-    } else {
-        address += (dict[*pos] & (ADDRESS_MASK >> 16)) << 16;
-        address += (dict[*pos + 1] & 0xFF) << 8;
-        address += (dict[*pos + 2] & 0xFF);
-        *pos += 3;
-    }
-    return address;
-}
-
-inline int Dictionary::getFreq(const unsigned char *dict,
-        const bool isLatestDictVersion, int *pos) {
-    int freq = dict[(*pos)++] & 0xFF;
-    if (isLatestDictVersion) {
-        // skipping bigram
-        int bigramExist = (dict[*pos] & FLAG_BIGRAM_READ);
-        if (bigramExist > 0) {
-            int nextBigramExist = 1;
-            while (nextBigramExist > 0) {
-                (*pos) += 3;
-                nextBigramExist = (dict[(*pos)++] & FLAG_BIGRAM_CONTINUED);
-            }
-        } else {
-            (*pos)++;
-        }
-    }
-    return freq;
-}
-
-inline int Dictionary::wideStrLen(unsigned short *str) {
-    if (!str) return 0;
-    unsigned short *end = str;
-    while (*end)
-        end++;
-    return end - str;
-}
-
-inline int Dictionary::setDictionaryValues(const unsigned char *dict,
-        const bool isLatestDictVersion, const int pos, unsigned short *c,int *childrenPosition,
-        bool *terminal, int *freq) {
-    int position = pos;
-    // -- at char
-    *c = Dictionary::getChar(dict, &position);
-    // -- at flag/add
-    *terminal = Dictionary::getTerminal(dict, &position);
-    *childrenPosition = Dictionary::getAddress(dict, &position);
-    // -- after address or flag
-    *freq = (*terminal) ? Dictionary::getFreq(dict, isLatestDictVersion, &position) : 1;
-    // returns next sibling's position
-    return position;
-}
-
-
-inline unsigned short Dictionary::toBaseLowerCase(unsigned short c) {
-    if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) {
-        c = BASE_CHARS[c];
-    }
-    if (c >='A' && c <= 'Z') {
-        c |= 32;
-    } else if (c > 127) {
-        c = latin_tolower(c);
-    }
-    return c;
-}
-
-} // namespace latinime
-
-#endif // LATINIME_DICTIONARY_H
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
deleted file mode 100644
index 763a3a1..0000000
--- a/native/src/proximity_info.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-#define LOG_TAG "LatinIME: proximity_info.cpp"
-
-#include "dictionary.h"
-#include "proximity_info.h"
-
-namespace latinime {
-
-inline void copyOrFillZero(void *to, const void *from, size_t size) {
-    if (from) {
-        memcpy(to, from, size);
-    } else {
-        memset(to, 0, size);
-    }
-}
-
-ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
-        const int keyboardHeight, const int gridWidth, const int gridHeight,
-        const uint32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
-        const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
-        const int32_t *keyCharCodes, const float *sweetSpotCenterXs, const float *sweetSpotCenterYs,
-        const float *sweetSpotRadii)
-        : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
-          KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
-          CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
-          CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
-          KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
-          HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
-                  && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
-                  && sweetSpotCenterYs && sweetSpotRadii),
-          mInputXCoordinates(NULL), mInputYCoordinates(NULL),
-          mTouchPositionCorrectionEnabled(false) {
-    const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
-    mProximityCharsArray = new uint32_t[proximityGridLength];
-    if (DEBUG_PROXIMITY_INFO) {
-        LOGI("Create proximity info array %d", proximityGridLength);
-    }
-    memcpy(mProximityCharsArray, proximityCharsArray,
-            proximityGridLength * sizeof(mProximityCharsArray[0]));
-    const int normalizedSquaredDistancesLength =
-            MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH_INTERNAL;
-    mNormalizedSquaredDistances = new int[normalizedSquaredDistancesLength];
-    for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
-        mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
-    }
-
-    copyOrFillZero(mKeyXCoordinates, keyXCoordinates, KEY_COUNT * sizeof(mKeyXCoordinates[0]));
-    copyOrFillZero(mKeyYCoordinates, keyYCoordinates, KEY_COUNT * sizeof(mKeyYCoordinates[0]));
-    copyOrFillZero(mKeyWidths, keyWidths, KEY_COUNT * sizeof(mKeyWidths[0]));
-    copyOrFillZero(mKeyHeights, keyHeights, KEY_COUNT * sizeof(mKeyHeights[0]));
-    copyOrFillZero(mKeyCharCodes, keyCharCodes, KEY_COUNT * sizeof(mKeyCharCodes[0]));
-    copyOrFillZero(mSweetSpotCenterXs, sweetSpotCenterXs,
-            KEY_COUNT * sizeof(mSweetSpotCenterXs[0]));
-    copyOrFillZero(mSweetSpotCenterYs, sweetSpotCenterYs,
-            KEY_COUNT * sizeof(mSweetSpotCenterYs[0]));
-    copyOrFillZero(mSweetSpotRadii, sweetSpotRadii, KEY_COUNT * sizeof(mSweetSpotRadii[0]));
-
-    initializeCodeToKeyIndex();
-}
-
-// Build the reversed look up table from the char code to the index in mKeyXCoordinates,
-// mKeyYCoordinates, mKeyWidths, mKeyHeights, mKeyCharCodes.
-void ProximityInfo::initializeCodeToKeyIndex() {
-    memset(mCodeToKeyIndex, -1, (MAX_CHAR_CODE + 1) * sizeof(mCodeToKeyIndex[0]));
-    for (int i = 0; i < KEY_COUNT; ++i) {
-        const int code = mKeyCharCodes[i];
-        if (0 <= code && code <= MAX_CHAR_CODE) {
-            mCodeToKeyIndex[code] = i;
-        }
-    }
-}
-
-ProximityInfo::~ProximityInfo() {
-    delete[] mNormalizedSquaredDistances;
-    delete[] mProximityCharsArray;
-}
-
-inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
-    return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
-            * MAX_PROXIMITY_CHARS_SIZE;
-}
-
-bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
-    const int startIndex = getStartIndexFromCoordinates(x, y);
-    if (DEBUG_PROXIMITY_INFO) {
-        LOGI("hasSpaceProximity: index %d", startIndex);
-    }
-    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-        if (DEBUG_PROXIMITY_INFO) {
-            LOGI("Index: %d", mProximityCharsArray[startIndex + i]);
-        }
-        if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
-            return true;
-        }
-    }
-    return false;
-}
-
-// TODO: Calculate nearby codes here.
-void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength,
-        const int* xCoordinates, const int* yCoordinates) {
-    mInputCodes = inputCodes;
-    mInputXCoordinates = xCoordinates;
-    mInputYCoordinates = yCoordinates;
-    mTouchPositionCorrectionEnabled =
-            HAS_TOUCH_POSITION_CORRECTION_DATA && xCoordinates && yCoordinates;
-    mInputLength = inputLength;
-    for (int i = 0; i < inputLength; ++i) {
-        mPrimaryInputWord[i] = getPrimaryCharAt(i);
-    }
-    mPrimaryInputWord[inputLength] = 0;
-    for (int i = 0; i < mInputLength; ++i) {
-        const int *proximityChars = getProximityCharsAt(i);
-        for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE && proximityChars[j] > 0; ++j) {
-            const int currentChar = proximityChars[j];
-            const int keyIndex = getKeyIndex(currentChar);
-            const float squaredDistance = calculateNormalizedSquaredDistance(keyIndex, i);
-            if (squaredDistance >= 0.0f) {
-                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] =
-                        (int)(squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
-            } else {
-                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] = (j == 0)
-                        ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO
-                        : PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
-            }
-        }
-    }
-}
-
-inline float square(const float x) { return x * x; }
-
-float ProximityInfo::calculateNormalizedSquaredDistance(
-        const int keyIndex, const int inputIndex) const {
-    static const float NOT_A_DISTANCE_FLOAT = -1.0f;
-    if (keyIndex == NOT_A_INDEX) {
-        return NOT_A_DISTANCE_FLOAT;
-    }
-    if (!hasSweetSpotData(keyIndex)) {
-        return NOT_A_DISTANCE_FLOAT;
-    }
-    const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, inputIndex);
-    const float squaredRadius = square(mSweetSpotRadii[keyIndex]);
-    return squaredDistance / squaredRadius;
-}
-
-int ProximityInfo::getKeyIndex(const int c) const {
-    if (KEY_COUNT == 0 || !mInputXCoordinates || !mInputYCoordinates) {
-        // We do not have the coordinate data
-        return NOT_A_INDEX;
-    }
-    const unsigned short baseLowerC = Dictionary::toBaseLowerCase(c);
-    if (baseLowerC > MAX_CHAR_CODE) {
-        return NOT_A_INDEX;
-    }
-    return mCodeToKeyIndex[baseLowerC];
-}
-
-float ProximityInfo::calculateSquaredDistanceFromSweetSpotCenter(
-        const int keyIndex, const int inputIndex) const {
-    const float sweetSpotCenterX = mSweetSpotCenterXs[keyIndex];
-    const float sweetSpotCenterY = mSweetSpotCenterYs[keyIndex];
-    const float inputX = (float)mInputXCoordinates[inputIndex];
-    const float inputY = (float)mInputYCoordinates[inputIndex];
-    return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
-}
-
-inline const int* ProximityInfo::getProximityCharsAt(const int index) const {
-    return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE);
-}
-
-unsigned short ProximityInfo::getPrimaryCharAt(const int index) const {
-    return getProximityCharsAt(index)[0];
-}
-
-inline bool ProximityInfo::existsCharInProximityAt(const int index, const int c) const {
-    const int *chars = getProximityCharsAt(index);
-    int i = 0;
-    while (chars[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE) {
-        if (chars[i++] == c) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool ProximityInfo::existsAdjacentProximityChars(const int index) const {
-    if (index < 0 || index >= mInputLength) return false;
-    const int currentChar = getPrimaryCharAt(index);
-    const int leftIndex = index - 1;
-    if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
-        return true;
-    }
-    const int rightIndex = index + 1;
-    if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
-        return true;
-    }
-    return false;
-}
-
-// In the following function, c is the current character of the dictionary word
-// currently examined.
-// currentChars is an array containing the keys close to the character the
-// user actually typed at the same position. We want to see if c is in it: if so,
-// then the word contains at that position a character close to what the user
-// typed.
-// What the user typed is actually the first character of the array.
-// proximityIndex is a pointer to the variable where getMatchedProximityId returns
-// the index of c in the proximity chars of the input index.
-// Notice : accented characters do not have a proximity list, so they are alone
-// in their list. The non-accented version of the character should be considered
-// "close", but not the other keys close to the non-accented version.
-ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int index,
-        const unsigned short c, const bool checkProximityChars, int *proximityIndex) const {
-    const int *currentChars = getProximityCharsAt(index);
-    const int firstChar = currentChars[0];
-    const unsigned short baseLowerC = Dictionary::toBaseLowerCase(c);
-
-    // The first char in the array is what user typed. If it matches right away,
-    // that means the user typed that same char for this pos.
-    if (firstChar == baseLowerC || firstChar == c) {
-        return EQUIVALENT_CHAR;
-    }
-
-    if (!checkProximityChars) return UNRELATED_CHAR;
-
-    // If the non-accented, lowercased version of that first character matches c,
-    // then we have a non-accented version of the accented character the user
-    // typed. Treat it as a close char.
-    if (Dictionary::toBaseLowerCase(firstChar) == baseLowerC)
-        return NEAR_PROXIMITY_CHAR;
-
-    // Not an exact nor an accent-alike match: search the list of close keys
-    int j = 1;
-    while (j < MAX_PROXIMITY_CHARS_SIZE && currentChars[j] > 0) {
-        const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
-        if (matched) {
-            if (proximityIndex) {
-                *proximityIndex = j;
-            }
-            return NEAR_PROXIMITY_CHAR;
-        }
-        ++j;
-    }
-
-    // Was not included, signal this as an unrelated character.
-    return UNRELATED_CHAR;
-}
-
-bool ProximityInfo::sameAsTyped(const unsigned short *word, int length) const {
-    if (length != mInputLength) {
-        return false;
-    }
-    const int *inputCodes = mInputCodes;
-    while (length--) {
-        if ((unsigned int) *inputCodes != (unsigned int) *word) {
-            return false;
-        }
-        inputCodes += MAX_PROXIMITY_CHARS_SIZE;
-        word++;
-    }
-    return true;
-}
-
-const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
-const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
-const int ProximityInfo::MAX_KEY_COUNT_IN_A_KEYBOARD;
-const int ProximityInfo::MAX_CHAR_CODE;
-
-} // namespace latinime
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
deleted file mode 100644
index 8eb5a97..0000000
--- a/native/src/unigram_dictionary.cpp
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
-**
-** 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.
-*/
-
-#include <assert.h>
-#include <string.h>
-
-#define LOG_TAG "LatinIME: unigram_dictionary.cpp"
-
-#include "char_utils.h"
-#include "dictionary.h"
-#include "unigram_dictionary.h"
-
-#include "binary_format.h"
-
-namespace latinime {
-
-const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] =
-        { { 'a', 'e' },
-        { 'o', 'e' },
-        { 'u', 'e' } };
-
-// TODO: check the header
-UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultiplier,
-        int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars,
-        const bool isLatestDictVersion)
-    : DICT_ROOT(streamStart + NEW_DICTIONARY_HEADER_SIZE),
-    MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords),
-    MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion),
-    TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
-      // TODO : remove this variable.
-    ROOT_POS(0),
-    BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(int)),
-    MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
-    if (DEBUG_DICT) {
-        LOGI("UnigramDictionary - constructor");
-    }
-    mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
-}
-
-UnigramDictionary::~UnigramDictionary() {
-    delete mCorrection;
-}
-
-static inline unsigned int getCodesBufferSize(const int* codes, const int codesSize,
-        const int MAX_PROXIMITY_CHARS) {
-    return sizeof(*codes) * MAX_PROXIMITY_CHARS * codesSize;
-}
-
-bool UnigramDictionary::isDigraph(const int* codes, const int i, const int codesSize) const {
-
-    // There can't be a digraph if we don't have at least 2 characters to examine
-    if (i + 2 > codesSize) return false;
-
-    // Search for the first char of some digraph
-    int lastDigraphIndex = -1;
-    const int thisChar = codes[i * MAX_PROXIMITY_CHARS];
-    for (lastDigraphIndex = sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]) - 1;
-            lastDigraphIndex >= 0; --lastDigraphIndex) {
-        if (thisChar == GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].first) break;
-    }
-    // No match: return early
-    if (lastDigraphIndex < 0) return false;
-
-    // It's an interesting digraph if the second char matches too.
-    return GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS];
-}
-
-// Mostly the same arguments as the non-recursive version, except:
-// codes is the original value. It points to the start of the work buffer, and gets passed as is.
-// codesSize is the size of the user input (thus, it is the size of codesSrc).
-// codesDest is the current point in the work buffer.
-// codesSrc is the current point in the user-input, original, content-unmodified buffer.
-// codesRemain is the remaining size in codesSrc.
-void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
-        const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
-        const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
-        const int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies) {
-
-    if (currentDepth < MAX_UMLAUT_SEARCH_DEPTH) {
-        for (int i = 0; i < codesRemain; ++i) {
-            if (isDigraph(codesSrc, i, codesRemain)) {
-                // Found a digraph. We will try both spellings. eg. the word is "pruefen"
-
-                // Copy the word up to the first char of the digraph, then continue processing
-                // on the remaining part of the word, skipping the second char of the digraph.
-                // In our example, copy "pru" and continue running on "fen"
-                // Make i the index of the second char of the digraph for simplicity. Forgetting
-                // to do that results in an infinite recursion so take care!
-                ++i;
-                memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
-                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
-                        codesBuffer, codesBufferSize, flags,
-                        codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
-                        currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, outWords,
-                        frequencies);
-
-                // Copy the second char of the digraph in place, then continue processing on
-                // the remaining part of the word.
-                // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
-                memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS,
-                        BYTES_IN_ONE_CHAR);
-                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
-                        codesBuffer, codesBufferSize, flags, codesSrc + i * MAX_PROXIMITY_CHARS,
-                        codesRemain - i, currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS,
-                        outWords, frequencies);
-                return;
-            }
-        }
-    }
-
-    // If we come here, we hit the end of the word: let's check it against the dictionary.
-    // In our example, we'll come here once for "prufen" and then once for "pruefen".
-    // If the word contains several digraphs, we'll come it for the product of them.
-    // eg. if the word is "ueberpruefen" we'll test, in order, against
-    // "uberprufen", "uberpruefen", "ueberprufen", "ueberpruefen".
-    const unsigned int remainingBytes = BYTES_IN_ONE_CHAR * codesRemain;
-    if (0 != remainingBytes)
-        memcpy(codesDest, codesSrc, remainingBytes);
-
-    getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-            (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies,
-            flags);
-}
-
-int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-        const int *ycoordinates, const int *codes, const int codesSize, const int flags,
-        unsigned short *outWords, int *frequencies) {
-
-    if (REQUIRES_GERMAN_UMLAUT_PROCESSING & flags)
-    { // Incrementally tune the word and try all possibilities
-        int codesBuffer[getCodesBufferSize(codes, codesSize, MAX_PROXIMITY_CHARS)];
-        getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
-    } else { // Normal processing
-        getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
-                outWords, frequencies, flags);
-    }
-
-    PROF_START(20);
-    // Get the word count
-    int suggestedWordsCount = 0;
-    while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
-        suggestedWordsCount++;
-    }
-
-    if (DEBUG_DICT) {
-        LOGI("Returning %d words", suggestedWordsCount);
-        /// Print the returned words
-        for (int j = 0; j < suggestedWordsCount; ++j) {
-#ifdef FLAG_DBG
-            short unsigned int* w = mOutputChars + j * MAX_WORD_LENGTH;
-            char s[MAX_WORD_LENGTH];
-            for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i];
-            LOGI("%s %i", s, mFrequencies[j]);
-#endif
-        }
-    }
-    PROF_END(20);
-    PROF_CLOSE;
-    return suggestedWordsCount;
-}
-
-void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
-        const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize,
-        unsigned short *outWords, int *frequencies, const int flags) {
-
-    PROF_OPEN;
-    PROF_START(0);
-    initSuggestions(
-            proximityInfo, xcoordinates, ycoordinates, codes, codesSize, outWords, frequencies);
-    if (DEBUG_DICT) assert(codesSize == mInputLength);
-
-    const int maxDepth = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
-    mCorrection->initCorrection(mProximityInfo, mInputLength, maxDepth);
-    PROF_END(0);
-
-    const bool useFullEditDistance = USE_FULL_EDIT_DISTANCE & flags;
-    // TODO: remove
-    PROF_START(1);
-    getSuggestionCandidates(useFullEditDistance);
-    PROF_END(1);
-
-    PROF_START(2);
-    // Note: This line is intentionally left blank
-    PROF_END(2);
-
-    PROF_START(3);
-    // Note: This line is intentionally left blank
-    PROF_END(3);
-
-    PROF_START(4);
-    // Note: This line is intentionally left blank
-    PROF_END(4);
-
-    PROF_START(5);
-    // Suggestions with missing space
-    if (SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER
-            && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
-        for (int i = 1; i < codesSize; ++i) {
-            if (DEBUG_DICT) {
-                LOGI("--- Suggest missing space characters %d", i);
-            }
-            getMissingSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
-        }
-    }
-    PROF_END(5);
-
-    PROF_START(6);
-    if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY && proximityInfo) {
-        // The first and last "mistyped spaces" are taken care of by excessive character handling
-        for (int i = 1; i < codesSize - 1; ++i) {
-            if (DEBUG_DICT) {
-                LOGI("--- Suggest words with proximity space %d", i);
-            }
-            const int x = xcoordinates[i];
-            const int y = ycoordinates[i];
-            if (DEBUG_PROXIMITY_INFO) {
-                LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
-                        i, x, y, proximityInfo->hasSpaceProximity(x, y));
-            }
-            if (proximityInfo->hasSpaceProximity(x, y)) {
-                getMistypedSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
-            }
-        }
-    }
-    PROF_END(6);
-}
-
-void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
-        const int *yCoordinates, const int *codes, const int codesSize,
-        unsigned short *outWords, int *frequencies) {
-    if (DEBUG_DICT) {
-        LOGI("initSuggest");
-    }
-    mFrequencies = frequencies;
-    mOutputChars = outWords;
-    mInputLength = codesSize;
-    proximityInfo->setInputParams(codes, codesSize, xCoordinates, yCoordinates);
-    mProximityInfo = proximityInfo;
-}
-
-static inline void registerNextLetter(unsigned short c, int *nextLetters, int nextLettersSize) {
-    if (c < nextLettersSize) {
-        nextLetters[c]++;
-    }
-}
-
-// TODO: We need to optimize addWord by using STL or something
-// TODO: This needs to take an const unsigned short* and not tinker with its contents
-bool UnigramDictionary::addWord(unsigned short *word, int length, int frequency) {
-    word[length] = 0;
-    if (DEBUG_DICT && DEBUG_SHOW_FOUND_WORD) {
-#ifdef FLAG_DBG
-        char s[length + 1];
-        for (int i = 0; i <= length; i++) s[i] = word[i];
-        LOGI("Found word = %s, freq = %d", s, frequency);
-#endif
-    }
-    if (length > MAX_WORD_LENGTH) {
-        if (DEBUG_DICT) {
-            LOGI("Exceeded max word length.");
-        }
-        return false;
-    }
-
-    // Find the right insertion point
-    int insertAt = 0;
-    while (insertAt < MAX_WORDS) {
-        // TODO: How should we sort words with the same frequency?
-        if (frequency > mFrequencies[insertAt]) {
-            break;
-        }
-        insertAt++;
-    }
-    if (insertAt < MAX_WORDS) {
-        if (DEBUG_DICT) {
-#ifdef FLAG_DBG
-            char s[length + 1];
-            for (int i = 0; i <= length; i++) s[i] = word[i];
-            LOGI("Added word = %s, freq = %d, %d", s, frequency, S_INT_MAX);
-#endif
-        }
-        memmove((char*) mFrequencies + (insertAt + 1) * sizeof(mFrequencies[0]),
-               (char*) mFrequencies + insertAt * sizeof(mFrequencies[0]),
-               (MAX_WORDS - insertAt - 1) * sizeof(mFrequencies[0]));
-        mFrequencies[insertAt] = frequency;
-        memmove((char*) mOutputChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
-               (char*) mOutputChars + insertAt * MAX_WORD_LENGTH * sizeof(short),
-               (MAX_WORDS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
-        unsigned short *dest = mOutputChars + insertAt * MAX_WORD_LENGTH;
-        while (length--) {
-            *dest++ = *word++;
-        }
-        *dest = 0; // NULL terminate
-        if (DEBUG_DICT) {
-            LOGI("Added word at %d", insertAt);
-        }
-        return true;
-    }
-    return false;
-}
-
-static const char QUOTE = '\'';
-static const char SPACE = ' ';
-
-void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance) {
-    // TODO: Remove setCorrectionParams
-    mCorrection->setCorrectionParams(0, 0, 0,
-            -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance);
-    int rootPosition = ROOT_POS;
-    // Get the number of children of root, then increment the position
-    int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition);
-    int outputIndex = 0;
-
-    mCorrection->initCorrectionState(rootPosition, childCount, (mInputLength <= 0));
-
-    // Depth first search
-    while (outputIndex >= 0) {
-        if (mCorrection->initProcessState(outputIndex)) {
-            int siblingPos = mCorrection->getTreeSiblingPos(outputIndex);
-            int firstChildPos;
-
-            const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
-                    mCorrection, &childCount, &firstChildPos, &siblingPos);
-            // Update next sibling pos
-            mCorrection->setTreeSiblingPos(outputIndex, siblingPos);
-
-            if (needsToTraverseChildrenNodes) {
-                // Goes to child node
-                outputIndex = mCorrection->goDownTree(outputIndex, childCount, firstChildPos);
-            }
-        } else {
-            // Goes to parent sibling node
-            outputIndex = mCorrection->getTreeParentIndex(outputIndex);
-        }
-    }
-}
-
-void UnigramDictionary::getMissingSpaceWords(
-        const int inputLength, const int missingSpacePos, Correction *correction,
-        const bool useFullEditDistance) {
-    correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
-            -1 /* transposedPos */, -1 /* spaceProximityPos */, missingSpacePos,
-            useFullEditDistance);
-    getSplitTwoWordsSuggestion(inputLength, correction);
-}
-
-void UnigramDictionary::getMistypedSpaceWords(
-        const int inputLength, const int spaceProximityPos, Correction *correction,
-        const bool useFullEditDistance) {
-    correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
-            -1 /* transposedPos */, spaceProximityPos, -1 /* missingSpacePos */,
-            useFullEditDistance);
-    getSplitTwoWordsSuggestion(inputLength, correction);
-}
-
-inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c,
-        const int inputIndex, const int skipPos, const int depth) {
-    const unsigned short userTypedChar = mProximityInfo->getPrimaryCharAt(inputIndex);
-    // Skip the ' or other letter and continue deeper
-    return (c == QUOTE && userTypedChar != QUOTE) || skipPos == depth;
-}
-
-inline void UnigramDictionary::onTerminal(const int freq, Correction *correction) {
-    int wordLength;
-    unsigned short* wordPointer;
-    const int finalFreq = correction->getFinalFreq(freq, &wordPointer, &wordLength);
-    if (finalFreq >= 0) {
-        addWord(wordPointer, wordLength, finalFreq);
-    }
-}
-
-void UnigramDictionary::getSplitTwoWordsSuggestion(
-        const int inputLength, Correction* correction) {
-    const int spaceProximityPos = correction->getSpaceProximityPos();
-    const int missingSpacePos = correction->getMissingSpacePos();
-    if (DEBUG_DICT) {
-        int inputCount = 0;
-        if (spaceProximityPos >= 0) ++inputCount;
-        if (missingSpacePos >= 0) ++inputCount;
-        assert(inputCount <= 1);
-    }
-    const bool isSpaceProximity = spaceProximityPos >= 0;
-    const int firstWordStartPos = 0;
-    const int secondWordStartPos = isSpaceProximity ? (spaceProximityPos + 1) : missingSpacePos;
-    const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
-    const int secondWordLength = isSpaceProximity
-            ? (inputLength - spaceProximityPos - 1)
-            : (inputLength - missingSpacePos);
-
-    if (inputLength >= MAX_WORD_LENGTH) return;
-    if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
-            || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength)
-        return;
-
-    const int newWordLength = firstWordLength + secondWordLength + 1;
-    // Allocating variable length array on stack
-    unsigned short word[newWordLength];
-    const int firstFreq = getMostFrequentWordLike(firstWordStartPos, firstWordLength, mWord);
-    if (DEBUG_DICT) {
-        LOGI("First freq: %d", firstFreq);
-    }
-    if (firstFreq <= 0) return;
-
-    for (int i = 0; i < firstWordLength; ++i) {
-        word[i] = mWord[i];
-    }
-
-    const int secondFreq = getMostFrequentWordLike(secondWordStartPos, secondWordLength, mWord);
-    if (DEBUG_DICT) {
-        LOGI("Second  freq:  %d", secondFreq);
-    }
-    if (secondFreq <= 0) return;
-
-    word[firstWordLength] = SPACE;
-    for (int i = (firstWordLength + 1); i < newWordLength; ++i) {
-        word[i] = mWord[i - firstWordLength - 1];
-    }
-
-    const int pairFreq = mCorrection->getFreqForSplitTwoWords(firstFreq, secondFreq, word);
-    if (DEBUG_DICT) {
-        LOGI("Split two words:  %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
-    }
-    addWord(word, newWordLength, pairFreq);
-    return;
-}
-
-// Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
-// interface.
-inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
-        const int inputLength, unsigned short *word) {
-    uint16_t inWord[inputLength];
-
-    for (int i = 0; i < inputLength; ++i) {
-        inWord[i] = (uint16_t)mProximityInfo->getPrimaryCharAt(startInputIndex + i);
-    }
-    return getMostFrequentWordLikeInner(inWord, inputLength, word);
-}
-
-// This function will take the position of a character array within a CharGroup,
-// and check it actually like-matches the word in inWord starting at startInputIndex,
-// that is, it matches it with case and accents squashed.
-// The function returns true if there was a full match, false otherwise.
-// The function will copy on-the-fly the characters in the CharGroup to outNewWord.
-// It will also place the end position of the array in outPos; in outInputIndex,
-// it will place the index of the first char AFTER the match if there was a match,
-// and the initial position if there was not. It makes sense because if there was
-// a match we want to continue searching, but if there was not, we want to go to
-// the next CharGroup.
-// In and out parameters may point to the same location. This function takes care
-// not to use any input parameters after it wrote into its outputs.
-static inline bool testCharGroupForContinuedLikeness(const uint8_t flags,
-        const uint8_t* const root, const int startPos,
-        const uint16_t* const inWord, const int startInputIndex,
-        int32_t* outNewWord, int* outInputIndex, int* outPos) {
-    const bool hasMultipleChars = (0 != (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags));
-    int pos = startPos;
-    int32_t character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
-    int32_t baseChar = Dictionary::toBaseLowerCase(character);
-    const uint16_t wChar = Dictionary::toBaseLowerCase(inWord[startInputIndex]);
-
-    if (baseChar != wChar) {
-        *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos;
-        *outInputIndex = startInputIndex;
-        return false;
-    }
-    int inputIndex = startInputIndex;
-    outNewWord[inputIndex] = character;
-    if (hasMultipleChars) {
-        character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
-        while (NOT_A_CHARACTER != character) {
-            baseChar = Dictionary::toBaseLowerCase(character);
-            if (Dictionary::toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
-                *outPos = BinaryFormat::skipOtherCharacters(root, pos);
-                *outInputIndex = startInputIndex;
-                return false;
-            }
-            outNewWord[inputIndex] = character;
-            character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
-        }
-    }
-    *outInputIndex = inputIndex + 1;
-    *outPos = pos;
-    return true;
-}
-
-// This function is invoked when a word like the word searched for is found.
-// It will compare the frequency to the max frequency, and if greater, will
-// copy the word into the output buffer. In output value maxFreq, it will
-// write the new maximum frequency if it changed.
-static inline void onTerminalWordLike(const int freq, int32_t* newWord, const int length,
-        short unsigned int* outWord, int* maxFreq) {
-    if (freq > *maxFreq) {
-        for (int q = 0; q < length; ++q)
-            outWord[q] = newWord[q];
-        outWord[length] = 0;
-        *maxFreq = freq;
-    }
-}
-
-// Will find the highest frequency of the words like the one passed as an argument,
-// that is, everything that only differs by case/accents.
-int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t * const inWord,
-        const int length, short unsigned int* outWord) {
-    int32_t newWord[MAX_WORD_LENGTH_INTERNAL];
-    int depth = 0;
-    int maxFreq = -1;
-    const uint8_t* const root = DICT_ROOT;
-
-    mStackChildCount[0] = root[0];
-    mStackInputIndex[0] = 0;
-    mStackSiblingPos[0] = 1;
-    while (depth >= 0) {
-        const int charGroupCount = mStackChildCount[depth];
-        int pos = mStackSiblingPos[depth];
-        for (int charGroupIndex = charGroupCount - 1; charGroupIndex >= 0; --charGroupIndex) {
-            int inputIndex = mStackInputIndex[depth];
-            const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-            // Test whether all chars in this group match with the word we are searching for. If so,
-            // we want to traverse its children (or if the length match, evaluate its frequency).
-            // Note that this function will output the position regardless, but will only write
-            // into inputIndex if there is a match.
-            const bool isAlike = testCharGroupForContinuedLikeness(flags, root, pos, inWord,
-                    inputIndex, newWord, &inputIndex, &pos);
-            if (isAlike && (FLAG_IS_TERMINAL & flags) && (inputIndex == length)) {
-                const int frequency = BinaryFormat::readFrequencyWithoutMovingPointer(root, pos);
-                onTerminalWordLike(frequency, newWord, inputIndex, outWord, &maxFreq);
-            }
-            pos = BinaryFormat::skipFrequency(flags, pos);
-            const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
-            const int childrenNodePos = BinaryFormat::readChildrenPosition(root, flags, pos);
-            // If we had a match and the word has children, we want to traverse them. We don't have
-            // to traverse words longer than the one we are searching for, since they will not match
-            // anyway, so don't traverse unless inputIndex < length.
-            if (isAlike && (-1 != childrenNodePos) && (inputIndex < length)) {
-                // Save position for this depth, to get back to this once children are done
-                mStackChildCount[depth] = charGroupIndex;
-                mStackSiblingPos[depth] = siblingPos;
-                // Prepare stack values for next depth
-                ++depth;
-                int childrenPos = childrenNodePos;
-                mStackChildCount[depth] =
-                        BinaryFormat::getGroupCountAndForwardPointer(root, &childrenPos);
-                mStackSiblingPos[depth] = childrenPos;
-                mStackInputIndex[depth] = inputIndex;
-                pos = childrenPos;
-                // Go to the next depth level.
-                ++depth;
-                break;
-            } else {
-                // No match, or no children, or word too long to ever match: go the next sibling.
-                pos = siblingPos;
-            }
-        }
-        --depth;
-    }
-    return maxFreq;
-}
-
-bool UnigramDictionary::isValidWord(const uint16_t* const inWord, const int length) const {
-    return NOT_VALID_WORD != BinaryFormat::getTerminalPosition(DICT_ROOT, inWord, length);
-}
-
-// TODO: remove this function.
-int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offset,
-        int length) const {
-    return -1;
-}
-
-// ProcessCurrentNode returns a boolean telling whether to traverse children nodes or not.
-// If the return value is false, then the caller should read in the output "nextSiblingPosition"
-// to find out the address of the next sibling node and pass it to a new call of processCurrentNode.
-// It is worthy to note that when false is returned, the output values other than
-// nextSiblingPosition are undefined.
-// If the return value is true, then the caller must proceed to traverse the children of this
-// node. processCurrentNode will output the information about the children: their count in
-// newCount, their position in newChildrenPosition, the traverseAllNodes flag in
-// newTraverseAllNodes, the match weight into newMatchRate, the input index into newInputIndex, the
-// diffs into newDiffs, the sibling position in nextSiblingPosition, and the output index into
-// newOutputIndex. Please also note the following caveat: processCurrentNode does not know when
-// there aren't any more nodes at this level, it merely returns the address of the first byte after
-// the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any
-// given level, as output into newCount when traversing this level's parent.
-inline bool UnigramDictionary::processCurrentNode(const int initialPos,
-        Correction *correction, int *newCount,
-        int *newChildrenPosition, int *nextSiblingPosition) {
-    if (DEBUG_DICT) {
-        correction->checkState();
-    }
-    int pos = initialPos;
-
-    // Flags contain the following information:
-    // - Address type (MASK_GROUP_ADDRESS_TYPE) on two bits:
-    //   - FLAG_GROUP_ADDRESS_TYPE_{ONE,TWO,THREE}_BYTES means there are children and their address
-    //     is on the specified number of bytes.
-    //   - FLAG_GROUP_ADDRESS_TYPE_NOADDRESS means there are no children, and therefore no address.
-    // - FLAG_HAS_MULTIPLE_CHARS: whether this node has multiple char or not.
-    // - FLAG_IS_TERMINAL: whether this node is a terminal or not (it may still have children)
-    // - FLAG_HAS_BIGRAMS: whether this node has bigrams or not
-    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(DICT_ROOT, &pos);
-    const bool hasMultipleChars = (0 != (FLAG_HAS_MULTIPLE_CHARS & flags));
-    const bool isTerminalNode = (0 != (FLAG_IS_TERMINAL & flags));
-
-    bool needsToInvokeOnTerminal = false;
-
-    // This gets only ONE character from the stream. Next there will be:
-    // if FLAG_HAS_MULTIPLE CHARS: the other characters of the same node
-    // else if FLAG_IS_TERMINAL: the frequency
-    // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address
-    // Note that you can't have a node that both is not a terminal and has no children.
-    int32_t c = BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos);
-    assert(NOT_A_CHARACTER != c);
-
-    // We are going to loop through each character and make it look like it's a different
-    // node each time. To do that, we will process characters in this node in order until
-    // we find the character terminator. This is signalled by getCharCode* returning
-    // NOT_A_CHARACTER.
-    // As a special case, if there is only one character in this node, we must not read the
-    // next bytes so we will simulate the NOT_A_CHARACTER return by testing the flags.
-    // This way, each loop run will look like a "virtual node".
-    do {
-        // We prefetch the next char. If 'c' is the last char of this node, we will have
-        // NOT_A_CHARACTER in the next char. From this we can decide whether this virtual node
-        // should behave as a terminal or not and whether we have children.
-        const int32_t nextc = hasMultipleChars
-                ? BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos) : NOT_A_CHARACTER;
-        const bool isLastChar = (NOT_A_CHARACTER == nextc);
-        // If there are more chars in this nodes, then this virtual node is not a terminal.
-        // If we are on the last char, this virtual node is a terminal if this node is.
-        const bool isTerminal = isLastChar && isTerminalNode;
-
-        Correction::CorrectionType stateType = correction->processCharAndCalcState(
-                c, isTerminal);
-        if (stateType == Correction::TRAVERSE_ALL_ON_TERMINAL
-                || stateType == Correction::ON_TERMINAL) {
-            needsToInvokeOnTerminal = true;
-        } else if (stateType == Correction::UNRELATED) {
-            // We found that this is an unrelated character, so we should give up traversing
-            // this node and its children entirely.
-            // However we may not be on the last virtual node yet so we skip the remaining
-            // characters in this node, the frequency if it's there, read the next sibling
-            // position to output it, then return false.
-            // We don't have to output other values because we return false, as in
-            // "don't traverse children".
-            if (!isLastChar) {
-                pos = BinaryFormat::skipOtherCharacters(DICT_ROOT, pos);
-            }
-            pos = BinaryFormat::skipFrequency(flags, pos);
-            *nextSiblingPosition =
-                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-            return false;
-        }
-
-        // Prepare for the next character. Promote the prefetched char to current char - the loop
-        // will take care of prefetching the next. If we finally found our last char, nextc will
-        // contain NOT_A_CHARACTER.
-        c = nextc;
-    } while (NOT_A_CHARACTER != c);
-
-    if (isTerminalNode) {
-        if (needsToInvokeOnTerminal) {
-            // The frequency should be here, because we come here only if this is actually
-            // a terminal node, and we are on its last char.
-            const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
-            onTerminal(freq, mCorrection);
-        }
-
-        // If there are more chars in this node, then this virtual node has children.
-        // If we are on the last char, this virtual node has children if this node has.
-        const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
-
-        // This character matched the typed character (enough to traverse the node at least)
-        // so we just evaluated it. Now we should evaluate this virtual node's children - that
-        // is, if it has any. If it has no children, we're done here - so we skip the end of
-        // the node, output the siblings position, and return false "don't traverse children".
-        // Note that !hasChildren implies isLastChar, so we know we don't have to skip any
-        // remaining char in this group for there can't be any.
-        if (!hasChildren) {
-            pos = BinaryFormat::skipFrequency(flags, pos);
-            *nextSiblingPosition =
-                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-            return false;
-        }
-
-        // Optimization: Prune out words that are too long compared to how much was typed.
-        if (correction->needsToPrune()) {
-            pos = BinaryFormat::skipFrequency(flags, pos);
-            *nextSiblingPosition =
-                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-            if (DEBUG_DICT_FULL) {
-                LOGI("Traversing was pruned.");
-            }
-            return false;
-        }
-    }
-
-    // Now we finished processing this node, and we want to traverse children. If there are no
-    // children, we can't come here.
-    assert(BinaryFormat::hasChildrenInFlags(flags));
-
-    // If this node was a terminal it still has the frequency under the pointer (it may have been
-    // read, but not skipped - see readFrequencyWithoutMovingPointer).
-    // Next come the children position, then possibly attributes (attributes are bigrams only for
-    // now, maybe something related to shortcuts in the future).
-    // Once this is read, we still need to output the number of nodes in the immediate children of
-    // this node, so we read and output it before returning true, as in "please traverse children".
-    pos = BinaryFormat::skipFrequency(flags, pos);
-    int childrenPos = BinaryFormat::readChildrenPosition(DICT_ROOT, flags, pos);
-    *nextSiblingPosition = BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-    *newCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &childrenPos);
-    *newChildrenPosition = childrenPos;
-    return true;
-}
-
-} // namespace latinime
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
deleted file mode 100644
index ef9709a..0000000
--- a/native/src/unigram_dictionary.h
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_UNIGRAM_DICTIONARY_H
-#define LATINIME_UNIGRAM_DICTIONARY_H
-
-#include <stdint.h>
-#include "correction.h"
-#include "correction_state.h"
-#include "defines.h"
-#include "proximity_info.h"
-
-#ifndef NULL
-#define NULL 0
-#endif
-
-namespace latinime {
-
-class UnigramDictionary {
-
-public:
-
-    // Mask and flags for children address type selection.
-    static const int MASK_GROUP_ADDRESS_TYPE = 0xC0;
-    static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
-    static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
-    static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
-    static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
-
-    // Flag for single/multiple char group
-    static const int FLAG_HAS_MULTIPLE_CHARS = 0x20;
-
-    // Flag for terminal groups
-    static const int FLAG_IS_TERMINAL = 0x10;
-
-    // Flag for bigram presence
-    static const int FLAG_HAS_BIGRAMS = 0x04;
-
-    // Attribute (bigram/shortcut) related flags:
-    // Flag for presence of more attributes
-    static const int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
-    // Flag for sign of offset. If this flag is set, the offset value must be negated.
-    static const int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
-
-    // Mask for attribute frequency, stored on 4 bits inside the flags byte.
-    static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
-
-    // Mask and flags for attribute address type selection.
-    static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
-
-    UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultipler,
-            int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars,
-            const bool isLatestDictVersion);
-    bool isValidWord(const uint16_t* const inWord, const int length) const;
-    int getBigramPosition(int pos, unsigned short *word, int offset, int length) const;
-    int getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const int codesSize, const int flags,
-            unsigned short *outWords, int *frequencies);
-    virtual ~UnigramDictionary();
-
-private:
-
-    void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const int codesSize,
-            unsigned short *outWords, int *frequencies, const int flags);
-    bool isDigraph(const int* codes, const int i, const int codesSize) const;
-    void getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
-        const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
-        const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
-        const int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies);
-    void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const int codesSize,
-            unsigned short *outWords, int *frequencies);
-    void getSuggestionCandidates(const bool useFullEditDistance);
-    bool addWord(unsigned short *word, int length, int frequency);
-    void getSplitTwoWordsSuggestion(const int inputLength, Correction *correction);
-    void getMissingSpaceWords(const int inputLength, const int missingSpacePos,
-            Correction *correction, const bool useFullEditDistance);
-    void getMistypedSpaceWords(const int inputLength, const int spaceProximityPos,
-            Correction *correction, const bool useFullEditDistance);
-    void onTerminal(const int freq, Correction *correction);
-    bool needsToSkipCurrentNode(const unsigned short c,
-            const int inputIndex, const int skipPos, const int depth);
-    // Process a node by considering proximity, missing and excessive character
-    bool processCurrentNode(const int initialPos,
-            Correction *correction, int *newCount,
-            int *newChildPosition, int *nextSiblingPosition);
-    int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
-            unsigned short *word);
-    int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,
-            short unsigned int* outWord);
-
-    const uint8_t* const DICT_ROOT;
-    const int MAX_WORD_LENGTH;
-    const int MAX_WORDS;
-    const int MAX_PROXIMITY_CHARS;
-    const bool IS_LATEST_DICT_VERSION;
-    const int TYPED_LETTER_MULTIPLIER;
-    const int FULL_WORD_MULTIPLIER;
-    const int ROOT_POS;
-    const unsigned int BYTES_IN_ONE_CHAR;
-    const int MAX_UMLAUT_SEARCH_DEPTH;
-
-    // Flags for special processing
-    // Those *must* match the flags in BinaryDictionary.Flags.ALL_FLAGS in BinaryDictionary.java
-    // or something very bad (like, the apocalypse) will happen.
-    // Please update both at the same time.
-    enum {
-        REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1,
-        USE_FULL_EDIT_DISTANCE = 0x2
-    };
-    static const struct digraph_t { int first; int second; } GERMAN_UMLAUT_DIGRAPHS[];
-
-    int *mFrequencies;
-    unsigned short *mOutputChars;
-    ProximityInfo *mProximityInfo;
-    Correction *mCorrection;
-    int mInputLength;
-    // MAX_WORD_LENGTH_INTERNAL must be bigger than MAX_WORD_LENGTH
-    unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
-
-    int mStackChildCount[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
-    int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
-    int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
-};
-} // namespace latinime
-
-#endif // LATINIME_UNIGRAM_DICTIONARY_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 658e8e2..6634070 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,3 +1,17 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 210e814..38a2ecf 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -22,8 +22,6 @@
     <application>
         <uses-library android:name="android.test.runner" />
         <!-- meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" /-->
-        <uses-permission android:name="android.permission.READ_CONTACTS" />
-
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/data/bigramlist.xml b/tests/data/bigramlist.xml
index dd3f291..d3d8bb8 100644
--- a/tests/data/bigramlist.xml
+++ b/tests/data/bigramlist.xml
@@ -25,7 +25,7 @@
     <bi w1="about" count="3">
         <w w2="part" p="117" />
         <w w2="business" p="100" />
-        <w w2="being" p="10" />
+        <w w2="being" p="90" />
     </bi>
     <bi w1="business" count="1">
         <w w2="people" p="100" />
diff --git a/tests/res/raw/test.dict b/tests/res/raw/test.dict
deleted file mode 100644
index 6a5d6d7..0000000
--- a/tests/res/raw/test.dict
+++ /dev/null
Binary files differ
diff --git a/tests/res/values/donottranslate.xml b/tests/res/values/donottranslate.xml
new file mode 100644
index 0000000..1ca4451
--- /dev/null
+++ b/tests/res/values/donottranslate.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+    <string name="empty_string">""</string>
+    <string name="single_char">"a"</string>
+    <string name="space">" "</string>
+    <string name="single_label">"abc"</string>
+    <string name="spaces">"   "</string>
+    <string name="spaces_in_label">"a b c"</string>
+    <string name="spaces_at_beginning_of_label">" abc"</string>
+    <string name="spaces_at_end_of_label">"abc "</string>
+    <string name="label_surrounded_by_spaces">" abc "</string>
+    <string name="escaped_char">"\\a"</string>
+    <string name="escaped_comma">"\\,"</string>
+    <string name="escaped_comma_escape">"a\\,\\"</string>
+    <string name="escaped_escape">"\\\\"</string>
+    <string name="escaped_label">"a\\bc"</string>
+    <string name="escaped_label_at_beginning">"\\abc"</string>
+    <string name="escaped_label_at_end">"abc\\"</string>
+    <string name="escaped_label_with_comma">"a\\,c"</string>
+    <string name="escaped_label_with_comma_at_beginning">"\\,bc"</string>
+    <string name="escaped_label_with_comma_at_end">"ab\\,"</string>
+    <string name="escaped_label_with_successive">"\\,\\\\bc"</string>
+    <string name="escaped_label_with_escape">"a\\\\c"</string>
+    <string name="multiple_chars">"a,b,c"</string>
+    <string name="multiple_chars_surrounded_by_spaces">" a , b , c "</string>
+    <string name="multiple_labels">"abc,def,ghi"</string>
+    <string name="multiple_labels_surrounded_by_spaces">" abc , def , ghi "</string>
+    <string name="multiple_chars_with_comma">"a,\\,,c"</string>
+    <string name="multiple_chars_with_comma_surrounded_by_spaces">" a , \\, , c "</string>
+    <string name="multiple_labels_with_escape">"\\abc,d\\ef,gh\\i"</string>
+    <string name="multiple_labels_with_escape_surrounded_by_spaces">" \\abc , d\\ef , gh\\i "</string>
+    <string name="multiple_labels_with_comma_and_escape">"ab\\\\,d\\\\\\,,g\\,i"</string>
+    <string name="multiple_labels_with_comma_and_escape_surrounded_by_spaces">" ab\\\\ , d\\\\\\, , g\\,i "</string>
+    <string name="indirect_string">@string/multiple_chars</string>
+    <string name="indirect_string_with_literal">x,@string/multiple_chars,y</string>
+    <string name="infinite_indirection">infinite,@string/infinite_indirection,loop</string>
+</resources>
diff --git a/tests/src/com/android/inputmethod/compat/ArraysCompatUtilsTests.java b/tests/src/com/android/inputmethod/compat/ArraysCompatUtilsTests.java
deleted file mode 100644
index 93681b6..0000000
--- a/tests/src/com/android/inputmethod/compat/ArraysCompatUtilsTests.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.compat;
-
-import android.test.AndroidTestCase;
-
-public class ArraysCompatUtilsTests extends AndroidTestCase {
-    // See {@link tests.api.java.util.ArraysTest}.
-    private static final int ARRAY_SIZE = 100;
-    private final int[] mIntArray = new int[ARRAY_SIZE];
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        for (int counter = 0; counter < ARRAY_SIZE; counter++) {
-            mIntArray[counter] = counter;
-        }
-    }
-
-    public void testEmptyArray() {
-        final int index = ArraysCompatUtils.binarySearch(mIntArray, 0, 0, 0);
-        assertEquals("empty", ~0, index);
-        final int compat = ArraysCompatUtils.compatBinarySearch(mIntArray, 0, 0, 0);
-        assertEquals("empty compat", ~0, compat);
-    }
-
-    public void testEmptyRangeArray() {
-        final int mid = ARRAY_SIZE / 3;
-        final int index = ArraysCompatUtils.binarySearch(mIntArray, mid, mid, 1);
-        assertEquals("empty", ~mid, index);
-        final int compat = ArraysCompatUtils.compatBinarySearch(mIntArray, mid, mid, 1);
-        assertEquals("empty compat", ~mid, compat);
-    }
-
-    public void testFind() {
-        for (int counter = 0; counter < ARRAY_SIZE; counter++) {
-            final int index = ArraysCompatUtils.binarySearch(mIntArray, 0, ARRAY_SIZE, counter);
-            assertEquals("found", counter, index);
-        }
-        for (int counter = 0; counter < ARRAY_SIZE; counter++) {
-            final int compat = ArraysCompatUtils.compatBinarySearch(
-                    mIntArray, 0, ARRAY_SIZE, counter);
-            assertEquals("found compat", counter, compat);
-        }
-    }
-
-    public void testFindNegative() {
-        final int offset = ARRAY_SIZE / 2;
-        for (int counter = 0; counter < ARRAY_SIZE; counter++) {
-            mIntArray[counter] -= offset;
-        }
-        for (int counter = 0; counter < ARRAY_SIZE; counter++) {
-            final int index = ArraysCompatUtils.binarySearch(
-                    mIntArray, 0, ARRAY_SIZE, counter - offset);
-            assertEquals("found", counter, index);
-        }
-        for (int counter = 0; counter < ARRAY_SIZE; counter++) {
-            final int compat = ArraysCompatUtils.compatBinarySearch(
-                    mIntArray, 0, ARRAY_SIZE, counter - offset);
-            assertEquals("found compat", counter, compat);
-        }
-    }
-
-    public void testNotFountAtTop() {
-        final int index = ArraysCompatUtils.binarySearch(mIntArray, 0, ARRAY_SIZE, -1);
-        assertEquals("not found top", ~0, index);
-        final int compat = ArraysCompatUtils.compatBinarySearch(
-                    mIntArray, 0, ARRAY_SIZE, -1);
-        assertEquals("not found top compat", ~0, compat);
-    }
-
-    public void testNotFountAtEnd() {
-        final int index = ArraysCompatUtils.binarySearch(mIntArray, 0, ARRAY_SIZE, ARRAY_SIZE);
-        assertEquals("not found end", ~ARRAY_SIZE, index);
-        final int compat = ArraysCompatUtils.compatBinarySearch(
-                    mIntArray, 0, ARRAY_SIZE, ARRAY_SIZE);
-        assertEquals("not found end compat", ~ARRAY_SIZE, compat);
-    }
-
-    public void testNotFountAtMid() {
-        final int mid = ARRAY_SIZE / 3;
-        mIntArray[mid] = mIntArray[mid + 1];
-        final int index = ArraysCompatUtils.binarySearch(mIntArray, 0, ARRAY_SIZE, mid);
-        assertEquals("not found mid", ~mid, index);
-        final int compat = ArraysCompatUtils.compatBinarySearch(
-                    mIntArray, 0, ARRAY_SIZE, mid);
-        assertEquals("not found mid compat", ~mid, compat);
-    }
-}
diff --git a/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
deleted file mode 100644
index a143bba..0000000
--- a/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
+++ /dev/null
@@ -1,1416 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import com.android.inputmethod.keyboard.MiniKeyboard.Builder.MiniKeyboardParams;
-
-import android.test.AndroidTestCase;
-
-public class MiniKeyboardBuilderTests extends AndroidTestCase {
-    private static final int MAX_COLUMNS = 5;
-    private static final int WIDTH = 10;
-    private static final int HEIGHT = 10;
-
-    private static final int KEYBOARD_WIDTH = WIDTH * 10;
-    private static final int XPOS_L0 = WIDTH * 0;
-    private static final int XPOS_L1 = WIDTH * 1;
-    private static final int XPOS_L2 = WIDTH * 2;
-    private static final int XPOS_M0 = WIDTH * 5;
-    private static final int XPOS_R3 = WIDTH * 6;
-    private static final int XPOS_R2 = WIDTH * 7;
-    private static final int XPOS_R1 = WIDTH * 8;
-    private static final int XPOS_R0 = WIDTH * 9;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public void testLayoutError() {
-        MiniKeyboardParams params = null;
-        try {
-            params = new MiniKeyboardParams(10, MAX_COLUMNS + 1, WIDTH, HEIGHT, WIDTH * 2,
-                    WIDTH * MAX_COLUMNS);
-            fail("Should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Too small keyboard to hold mini keyboard.
-        }
-        assertNull("Too small keyboard to hold mini keyboard", params);
-    }
-
-    // Mini keyboard layout test.
-    // "[n]" represents n-th key position in mini keyboard.
-    // "[1]" is the default key.
-
-    // [1]
-    public void testLayout1KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("1 key M0 columns", 1, params.mNumColumns);
-        assertEquals("1 key M0 rows", 1, params.mNumRows);
-        assertEquals("1 key M0 left", 0, params.mLeftKeys);
-        assertEquals("1 key M0 right", 1, params.mRightKeys);
-        assertEquals("1 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key M0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |[1]
-    public void testLayout1KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("1 key L0 columns", 1, params.mNumColumns);
-        assertEquals("1 key L0 rows", 1, params.mNumRows);
-        assertEquals("1 key L0 left", 0, params.mLeftKeys);
-        assertEquals("1 key L0 right", 1, params.mRightKeys);
-        assertEquals("1 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [1]
-    public void testLayout1KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("1 key L1 columns", 1, params.mNumColumns);
-        assertEquals("1 key L1 rows", 1, params.mNumRows);
-        assertEquals("1 key L1 left", 0, params.mLeftKeys);
-        assertEquals("1 key L1 right", 1, params.mRightKeys);
-        assertEquals("1 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [1]
-    public void testLayout1KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("1 key L2 columns", 1, params.mNumColumns);
-        assertEquals("1 key L2 rows", 1, params.mNumRows);
-        assertEquals("1 key L2 left", 0, params.mLeftKeys);
-        assertEquals("1 key L2 right", 1, params.mRightKeys);
-        assertEquals("1 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // [1]|
-    public void testLayout1KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("1 key R0 columns", 1, params.mNumColumns);
-        assertEquals("1 key R0 rows", 1, params.mNumRows);
-        assertEquals("1 key R0 left", 0, params.mLeftKeys);
-        assertEquals("1 key R0 right", 1, params.mRightKeys);
-        assertEquals("1 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key R0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // [1] ___|
-    public void testLayout1KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("1 key R1 columns", 1, params.mNumColumns);
-        assertEquals("1 key R1 rows", 1, params.mNumRows);
-        assertEquals("1 key R1 left", 0, params.mLeftKeys);
-        assertEquals("1 key R1 right", 1, params.mRightKeys);
-        assertEquals("1 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key R1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // [1] ___ ___|
-    public void testLayout1KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(1, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("1 key R2 columns", 1, params.mNumColumns);
-        assertEquals("1 key R2 rows", 1, params.mNumRows);
-        assertEquals("1 key R2 left", 0, params.mLeftKeys);
-        assertEquals("1 key R2 right", 1, params.mRightKeys);
-        assertEquals("1 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("1 key R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // [1] [2]
-    public void testLayout2KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("2 key M0 columns", 2, params.mNumColumns);
-        assertEquals("2 key M0 rows", 1, params.mNumRows);
-        assertEquals("2 key M0 left", 0, params.mLeftKeys);
-        assertEquals("2 key M0 right", 2, params.mRightKeys);
-        assertEquals("2 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("2 key M0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |[1] [2]
-    public void testLayout2KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("2 key L0 columns", 2, params.mNumColumns);
-        assertEquals("2 key L0 rows", 1, params.mNumRows);
-        assertEquals("2 key L0 left", 0, params.mLeftKeys);
-        assertEquals("2 key L0 right", 2, params.mRightKeys);
-        assertEquals("2 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("2 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [1] [2]
-    public void testLayout2KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("2 key L1 columns", 2, params.mNumColumns);
-        assertEquals("2 key L1 rows", 1, params.mNumRows);
-        assertEquals("2 key L1 left", 0, params.mLeftKeys);
-        assertEquals("2 key L1 right", 2, params.mRightKeys);
-        assertEquals("2 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("2 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [1] [2]
-    public void testLayout2KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("2 key L2 columns", 2, params.mNumColumns);
-        assertEquals("2 key L2 rows", 1, params.mNumRows);
-        assertEquals("2 key L2 left", 0, params.mLeftKeys);
-        assertEquals("2 key L2 right", 2, params.mRightKeys);
-        assertEquals("2 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("2 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // [2] [1]|
-    public void testLayout2KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("2 key R0 columns", 2, params.mNumColumns);
-        assertEquals("2 key R0 rows", 1, params.mNumRows);
-        assertEquals("2 key R0 left", 1, params.mLeftKeys);
-        assertEquals("2 key R0 right", 1, params.mRightKeys);
-        assertEquals("2 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("2 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [2] [1] ___|
-    public void testLayout2KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("2 key R1 columns", 2, params.mNumColumns);
-        assertEquals("2 key R1 rows", 1, params.mNumRows);
-        assertEquals("2 key R1 left", 1, params.mLeftKeys);
-        assertEquals("2 key R1 right", 1, params.mRightKeys);
-        assertEquals("2 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("2 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [1] [2] ___ ___|
-    public void testLayout2KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(2, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("2 key R2 columns", 2, params.mNumColumns);
-        assertEquals("2 key R2 rows", 1, params.mNumRows);
-        assertEquals("2 key R2 left", 0, params.mLeftKeys);
-        assertEquals("2 key R2 right", 2, params.mRightKeys);
-        assertEquals("2 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("2 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("2 key R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // [3] [1] [2]
-    public void testLayout3KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("3 key columns", 3, params.mNumColumns);
-        assertEquals("3 key rows", 1, params.mNumRows);
-        assertEquals("3 key left", 1, params.mLeftKeys);
-        assertEquals("3 key right", 2, params.mRightKeys);
-        assertEquals("3 key [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key [2]", 1, params.getColumnPos(1));
-        assertEquals("3 key [3]", -1, params.getColumnPos(2));
-        assertEquals("3 key adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // |[1] [2] [3]
-    public void testLayout3KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("3 key L0 columns", 3, params.mNumColumns);
-        assertEquals("3 key L0 rows", 1, params.mNumRows);
-        assertEquals("3 key L0 left", 0, params.mLeftKeys);
-        assertEquals("3 key L0 right", 3, params.mRightKeys);
-        assertEquals("3 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("3 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("3 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [1] [2] [3]
-    public void testLayout3KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("3 key L1 columns", 3, params.mNumColumns);
-        assertEquals("3 key L1 rows", 1, params.mNumRows);
-        assertEquals("3 key L1 left", 0, params.mLeftKeys);
-        assertEquals("3 key L1 right", 3, params.mRightKeys);
-        assertEquals("3 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("3 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("3 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [3] [1] [2]
-    public void testLayout3KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("3 key L2 columns", 3, params.mNumColumns);
-        assertEquals("3 key L2 rows", 1, params.mNumRows);
-        assertEquals("3 key L2 left", 1, params.mLeftKeys);
-        assertEquals("3 key L2 right", 2, params.mRightKeys);
-        assertEquals("3 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("3 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("3 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [3] [2] [1]|
-    public void testLayout3KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("3 key R0 columns", 3, params.mNumColumns);
-        assertEquals("3 key R0 rows", 1, params.mNumRows);
-        assertEquals("3 key R0 left", 2, params.mLeftKeys);
-        assertEquals("3 key R0 right", 1, params.mRightKeys);
-        assertEquals("3 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("3 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("3 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // [3] [2] [1] ___|
-    public void testLayout3KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("3 key R1 columns", 3, params.mNumColumns);
-        assertEquals("3 key R1 rows", 1, params.mNumRows);
-        assertEquals("3 key R1 left", 2, params.mLeftKeys);
-        assertEquals("3 key R1 right", 1, params.mRightKeys);
-        assertEquals("3 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("3 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("3 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // [3] [1] [2] ___ ___|
-    public void testLayout3KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(3, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("3 key R2 columns", 3, params.mNumColumns);
-        assertEquals("3 key R2 rows", 1, params.mNumRows);
-        assertEquals("3 key R2 left", 1, params.mLeftKeys);
-        assertEquals("3 key R2 right", 2, params.mRightKeys);
-        assertEquals("3 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("3 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("3 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("3 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("3 key R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [3] [1] [2] [4]
-    public void testLayout4KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("4 key columns", 4, params.mNumColumns);
-        assertEquals("4 key rows", 1, params.mNumRows);
-        assertEquals("4 key left", 1, params.mLeftKeys);
-        assertEquals("4 key right", 3, params.mRightKeys);
-        assertEquals("4 key [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key [2]", 1, params.getColumnPos(1));
-        assertEquals("4 key [3]", -1, params.getColumnPos(2));
-        assertEquals("4 key [4]", 2, params.getColumnPos(3));
-        assertEquals("4 key adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // |[1] [2] [3] [4]
-    public void testLayout4KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("4 key L0 columns", 4, params.mNumColumns);
-        assertEquals("4 key L0 rows", 1, params.mNumRows);
-        assertEquals("4 key L0 left", 0, params.mLeftKeys);
-        assertEquals("4 key L0 right", 4, params.mRightKeys);
-        assertEquals("4 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("4 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("4 key L0 [4]", 3, params.getColumnPos(3));
-        assertEquals("4 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [1] [2] [3] [4]
-    public void testLayout4KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("4 key L1 columns", 4, params.mNumColumns);
-        assertEquals("4 key L1 rows", 1, params.mNumRows);
-        assertEquals("4 key L1 left", 0, params.mLeftKeys);
-        assertEquals("4 key L1 right", 4, params.mRightKeys);
-        assertEquals("4 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("4 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("4 key L1 [4]", 3, params.getColumnPos(3));
-        assertEquals("4 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [3] [1] [2] [4]
-    public void testLayout4KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("4 key L2 columns", 4, params.mNumColumns);
-        assertEquals("4 key L2 rows", 1, params.mNumRows);
-        assertEquals("4 key L2 left", 1, params.mLeftKeys);
-        assertEquals("4 key L2 right", 3, params.mRightKeys);
-        assertEquals("4 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("4 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("4 key L2 [4]", 2, params.getColumnPos(3));
-        assertEquals("4 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [4] [3] [2] [1]|
-    public void testLayout4KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("4 key R0 columns", 4, params.mNumColumns);
-        assertEquals("4 key R0 rows", 1, params.mNumRows);
-        assertEquals("4 key R0 left", 3, params.mLeftKeys);
-        assertEquals("4 key R0 right", 1, params.mRightKeys);
-        assertEquals("4 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("4 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("4 key R0 [4]", -3, params.getColumnPos(3));
-        assertEquals("4 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    // [4] [3] [2] [1] ___|
-    public void testLayout4KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("4 key R1 columns", 4, params.mNumColumns);
-        assertEquals("4 key R1 rows", 1, params.mNumRows);
-        assertEquals("4 key R1 left", 3, params.mLeftKeys);
-        assertEquals("4 key R1 right", 1, params.mRightKeys);
-        assertEquals("4 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("4 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("4 key R1 [4]", -3, params.getColumnPos(3));
-        assertEquals("4 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    // [4] [3] [1] [2] ___ ___|
-    public void testLayout4KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(4, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("4 key R2 columns", 4, params.mNumColumns);
-        assertEquals("4 key R2 rows", 1, params.mNumRows);
-        assertEquals("4 key R2 left", 2, params.mLeftKeys);
-        assertEquals("4 key R2 right", 2, params.mRightKeys);
-        assertEquals("4 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("4 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("4 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("4 key R2 [4]", -2, params.getColumnPos(3));
-        assertEquals("4 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("4 key R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // [5] [3] [1] [2] [4]
-    public void testLayout5KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("5 key columns", 5, params.mNumColumns);
-        assertEquals("5 key rows", 1, params.mNumRows);
-        assertEquals("5 key left", 2, params.mLeftKeys);
-        assertEquals("5 key right", 3, params.mRightKeys);
-        assertEquals("5 key [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key [2]", 1, params.getColumnPos(1));
-        assertEquals("5 key [3]", -1, params.getColumnPos(2));
-        assertEquals("5 key [4]", 2, params.getColumnPos(3));
-        assertEquals("5 key [5]", -2, params.getColumnPos(4));
-        assertEquals("5 key adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // |[1] [2] [3] [4] [5]
-    public void testLayout5KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("5 key L0 columns", 5, params.mNumColumns);
-        assertEquals("5 key L0 rows", 1, params.mNumRows);
-        assertEquals("5 key L0 left", 0, params.mLeftKeys);
-        assertEquals("5 key L0 right", 5, params.mRightKeys);
-        assertEquals("5 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("5 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("5 key L0 [4]", 3, params.getColumnPos(3));
-        assertEquals("5 key L0 [5]", 4, params.getColumnPos(4));
-        assertEquals("5 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [1] [2] [3] [4] [5]
-    public void testLayout5KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("5 key L1 columns", 5, params.mNumColumns);
-        assertEquals("5 key L1 rows", 1, params.mNumRows);
-        assertEquals("5 key L1 left", 0, params.mLeftKeys);
-        assertEquals("5 key L1 right", 5, params.mRightKeys);
-        assertEquals("5 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("5 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("5 key L1 [4]", 3, params.getColumnPos(3));
-        assertEquals("5 key L1 [5]", 4, params.getColumnPos(4));
-        assertEquals("5 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [3] [1] [2] [4] [5]
-    public void testLayout5KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("5 key L2 columns", 5, params.mNumColumns);
-        assertEquals("5 key L2 rows", 1, params.mNumRows);
-        assertEquals("5 key L2 left", 1, params.mLeftKeys);
-        assertEquals("5 key L2 right", 4, params.mRightKeys);
-        assertEquals("5 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("5 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("5 key L2 [4]", 2, params.getColumnPos(3));
-        assertEquals("5 key L2 [5]", 3, params.getColumnPos(4));
-        assertEquals("5 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [5] [4] [3] [2] [1]|
-    public void testLayout5KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("5 key R0 columns", 5, params.mNumColumns);
-        assertEquals("5 key R0 rows", 1, params.mNumRows);
-        assertEquals("5 key R0 left", 4, params.mLeftKeys);
-        assertEquals("5 key R0 right", 1, params.mRightKeys);
-        assertEquals("5 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("5 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("5 key R0 [4]", -3, params.getColumnPos(3));
-        assertEquals("5 key R0 [5]", -4, params.getColumnPos(4));
-        assertEquals("5 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    // [5] [4] [3] [2] [1] ___|
-    public void testLayout5KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("5 key R1 columns", 5, params.mNumColumns);
-        assertEquals("5 key R1 rows", 1, params.mNumRows);
-        assertEquals("5 key R1 left", 4, params.mLeftKeys);
-        assertEquals("5 key R1 right", 1, params.mRightKeys);
-        assertEquals("5 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("5 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("5 key R1 [4]", -3, params.getColumnPos(3));
-        assertEquals("5 key R1 [5]", -4, params.getColumnPos(4));
-        assertEquals("5 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    // [5] [4] [3] [1] [2] ___ ___|
-    public void testLayout5KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(5, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("5 key R2 columns", 5, params.mNumColumns);
-        assertEquals("5 key R2 rows", 1, params.mNumRows);
-        assertEquals("5 key R2 left", 3, params.mLeftKeys);
-        assertEquals("5 key R2 right", 2, params.mRightKeys);
-        assertEquals("5 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("5 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("5 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("5 key R2 [4]", -2, params.getColumnPos(3));
-        assertEquals("5 key R2 [5]", -3, params.getColumnPos(4));
-        assertEquals("5 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("5 key R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    // [6] [4] [5]
-    // [3] [1] [2]
-    public void testLayout6KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("6 key columns", 3, params.mNumColumns);
-        assertEquals("6 key rows", 2, params.mNumRows);
-        assertEquals("6 key left", 1, params.mLeftKeys);
-        assertEquals("6 key right", 2, params.mRightKeys);
-        assertEquals("6 key [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key [2]", 1, params.getColumnPos(1));
-        assertEquals("6 key [3]", -1, params.getColumnPos(2));
-        assertEquals("6 key [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key [5]", 1, params.getColumnPos(4));
-        assertEquals("6 key [6]", -1, params.getColumnPos(5));
-        assertEquals("6 key adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // |[4] [5] [6]
-    // |[1] [2] [3]
-    public void testLayout6KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("6 key L0 columns", 3, params.mNumColumns);
-        assertEquals("6 key L0 rows", 2, params.mNumRows);
-        assertEquals("6 key L0 left", 0, params.mLeftKeys);
-        assertEquals("6 key L0 right", 3, params.mRightKeys);
-        assertEquals("6 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("6 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("6 key L0 [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key L0 [5]", 1, params.getColumnPos(4));
-        assertEquals("6 key L0 [6]", 2, params.getColumnPos(5));
-        assertEquals("6 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [4] [5] [6]
-    // |___ [1] [2] [3]
-    public void testLayout6KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("6 key L1 columns", 3, params.mNumColumns);
-        assertEquals("6 key L1 rows", 2, params.mNumRows);
-        assertEquals("6 key L1 left", 0, params.mLeftKeys);
-        assertEquals("6 key L1 right", 3, params.mRightKeys);
-        assertEquals("6 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("6 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("6 key L1 [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key L1 [5]", 1, params.getColumnPos(4));
-        assertEquals("6 key L1 [6]", 2, params.getColumnPos(5));
-        assertEquals("6 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [6] [4] [5]
-    // |___ ___ [3] [1] [2]
-    public void testLayout6KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("6 key L2 columns", 3, params.mNumColumns);
-        assertEquals("6 key L2 rows", 2, params.mNumRows);
-        assertEquals("6 key L2 left", 1, params.mLeftKeys);
-        assertEquals("6 key L2 right", 2, params.mRightKeys);
-        assertEquals("6 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("6 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("6 key L2 [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key L2 [5]", 1, params.getColumnPos(4));
-        assertEquals("6 key L2 [6]", -1, params.getColumnPos(5));
-        assertEquals("6 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [6] [5] [4]|
-    // [3] [2] [1]|
-    public void testLayout6KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("6 key R0 columns", 3, params.mNumColumns);
-        assertEquals("6 key R0 rows", 2, params.mNumRows);
-        assertEquals("6 key R0 left", 2, params.mLeftKeys);
-        assertEquals("6 key R0 right", 1, params.mRightKeys);
-        assertEquals("6 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("6 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("6 key R0 [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key R0 [5]", -1, params.getColumnPos(4));
-        assertEquals("6 key R0 [6]", -2, params.getColumnPos(5));
-        assertEquals("6 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // [6] [5] [4] ___|
-    // [3] [2] [1] ___|
-    public void testLayout6KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("6 key R1 columns", 3, params.mNumColumns);
-        assertEquals("6 key R1 rows", 2, params.mNumRows);
-        assertEquals("6 key R1 left", 2, params.mLeftKeys);
-        assertEquals("6 key R1 right", 1, params.mRightKeys);
-        assertEquals("6 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("6 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("6 key R1 [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key R1 [5]", -1, params.getColumnPos(4));
-        assertEquals("6 key R1 [6]", -2, params.getColumnPos(5));
-        assertEquals("6 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // [6] [4] [5] ___ ___|
-    // [3] [1] [2] ___ ___|
-    public void testLayout6KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(6, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("6 key R2 columns", 3, params.mNumColumns);
-        assertEquals("6 key R2 rows", 2, params.mNumRows);
-        assertEquals("6 key R2 left", 1, params.mLeftKeys);
-        assertEquals("6 key R2 right", 2, params.mRightKeys);
-        assertEquals("6 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("6 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("6 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("6 key R2 [4]", 0, params.getColumnPos(3));
-        assertEquals("6 key R2 [5]", 1, params.getColumnPos(4));
-        assertEquals("6 key R2 [6]", -1, params.getColumnPos(5));
-        assertEquals("6 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("6 key R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    //   [7] [5] [6]
-    // [3] [1] [2] [4]
-    public void testLayout7KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("7 key columns", 4, params.mNumColumns);
-        assertEquals("7 key rows", 2, params.mNumRows);
-        assertEquals("7 key left", 1, params.mLeftKeys);
-        assertEquals("7 key right", 3, params.mRightKeys);
-        assertEquals("7 key [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key [2]", 1, params.getColumnPos(1));
-        assertEquals("7 key [3]", -1, params.getColumnPos(2));
-        assertEquals("7 key [4]", 2, params.getColumnPos(3));
-        assertEquals("7 key [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key [6]", 1, params.getColumnPos(5));
-        assertEquals("7 key [7]", -1, params.getColumnPos(6));
-        assertEquals("7 key adjust", 1, params.mTopRowAdjustment);
-        assertEquals("7 key default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // |[5] [6] [7]
-    // |[1] [2] [3] [4]
-    public void testLayout7KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("7 key L0 columns", 4, params.mNumColumns);
-        assertEquals("7 key L0 rows", 2, params.mNumRows);
-        assertEquals("7 key L0 left", 0, params.mLeftKeys);
-        assertEquals("7 key L0 right", 4, params.mRightKeys);
-        assertEquals("7 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("7 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("7 key L0 [4]", 3, params.getColumnPos(3));
-        assertEquals("7 key L0 [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key L0 [6]", 1, params.getColumnPos(5));
-        assertEquals("7 key L0 [7]", 2, params.getColumnPos(6));
-        assertEquals("7 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("7 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [5] [6] [7]
-    // |___ [1] [2] [3] [4]
-    public void testLayout7KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("7 key L1 columns", 4, params.mNumColumns);
-        assertEquals("7 key L1 rows", 2, params.mNumRows);
-        assertEquals("7 key L1 left", 0, params.mLeftKeys);
-        assertEquals("7 key L1 right", 4, params.mRightKeys);
-        assertEquals("7 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("7 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("7 key L1 [4]", 3, params.getColumnPos(3));
-        assertEquals("7 key L1 [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key L1 [6]", 1, params.getColumnPos(5));
-        assertEquals("7 key L1 [7]", 2, params.getColumnPos(6));
-        assertEquals("7 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("7 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___   [7] [5] [6]
-    // |___ ___ [3] [1] [2] [4]
-    public void testLayout7KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("7 key L2 columns", 4, params.mNumColumns);
-        assertEquals("7 key L2 rows", 2, params.mNumRows);
-        assertEquals("7 key L2 left", 1, params.mLeftKeys);
-        assertEquals("7 key L2 right", 3, params.mRightKeys);
-        assertEquals("7 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("7 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("7 key L2 [4]", 2, params.getColumnPos(3));
-        assertEquals("7 key L2 [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key L2 [6]", 1, params.getColumnPos(5));
-        assertEquals("7 key L2 [7]", -1, params.getColumnPos(6));
-        assertEquals("7 key L2 adjust", 1, params.mTopRowAdjustment);
-        assertEquals("7 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    //     [7] [6] [5]|
-    // [4] [3] [2] [1]|
-    public void testLayout7KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("7 key R0 columns", 4, params.mNumColumns);
-        assertEquals("7 key R0 rows", 2, params.mNumRows);
-        assertEquals("7 key R0 left", 3, params.mLeftKeys);
-        assertEquals("7 key R0 right", 1, params.mRightKeys);
-        assertEquals("7 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("7 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("7 key R0 [4]", -3, params.getColumnPos(3));
-        assertEquals("7 key R0 [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key R0 [6]", -1, params.getColumnPos(5));
-        assertEquals("7 key R0 [7]", -2, params.getColumnPos(6));
-        assertEquals("7 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("7 key R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    //     [7] [6] [5] ___|
-    // [4] [3] [2] [1] ___|
-    public void testLayout7KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("7 key R1 columns", 4, params.mNumColumns);
-        assertEquals("7 key R1 rows", 2, params.mNumRows);
-        assertEquals("7 key R1 left", 3, params.mLeftKeys);
-        assertEquals("7 key R1 right", 1, params.mRightKeys);
-        assertEquals("7 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("7 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("7 key R1 [4]", -3, params.getColumnPos(3));
-        assertEquals("7 key R1 [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key R1 [6]", -1, params.getColumnPos(5));
-        assertEquals("7 key R1 [7]", -2, params.getColumnPos(6));
-        assertEquals("7 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("7 key R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    //   [7] [5] [6]   ___ ___|
-    // [4] [3] [1] [2] ___ ___|
-    public void testLayout7KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("7 key R2 columns", 4, params.mNumColumns);
-        assertEquals("7 key R2 rows", 2, params.mNumRows);
-        assertEquals("7 key R2 left", 2, params.mLeftKeys);
-        assertEquals("7 key R2 right", 2, params.mRightKeys);
-        assertEquals("7 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("7 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("7 key R2 [4]", -2, params.getColumnPos(3));
-        assertEquals("7 key R2 [5]", 0, params.getColumnPos(4));
-        assertEquals("7 key R2 [6]", 1, params.getColumnPos(5));
-        assertEquals("7 key R2 [7]", -1, params.getColumnPos(6));
-        assertEquals("7 key R2 adjust", -1, params.mTopRowAdjustment);
-        assertEquals("7 key R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // [7] [6] [5] [3] [1] [2] [4] ___|
-    public void testLayout7KeyR3Max7() {
-        MiniKeyboardParams params = new MiniKeyboardParams(7, 7, WIDTH,
-                HEIGHT, XPOS_R3, KEYBOARD_WIDTH);
-        assertEquals("7 key R2 columns", 7, params.mNumColumns);
-        assertEquals("7 key R2 rows", 1, params.mNumRows);
-        assertEquals("7 key R2 left", 4, params.mLeftKeys);
-        assertEquals("7 key R2 right", 3, params.mRightKeys);
-        assertEquals("7 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("7 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("7 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("7 key R2 [4]", 2, params.getColumnPos(3));
-        assertEquals("7 key R2 [5]", -2, params.getColumnPos(4));
-        assertEquals("7 key R2 [6]", -3, params.getColumnPos(5));
-        assertEquals("7 key R2 [7]", -4, params.getColumnPos(6));
-        assertEquals("7 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("7 key R2 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    // [7] [5] [6] [8]
-    // [3] [1] [2] [4]
-    public void testLayout8KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("8 key M0 columns", 4, params.mNumColumns);
-        assertEquals("8 key M0 rows", 2, params.mNumRows);
-        assertEquals("8 key M0 left", 1, params.mLeftKeys);
-        assertEquals("8 key M0 right", 3, params.mRightKeys);
-        assertEquals("8 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("8 key M0 [3]", -1, params.getColumnPos(2));
-        assertEquals("8 key M0 [4]", 2, params.getColumnPos(3));
-        assertEquals("8 key M0 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key M0 [6]", 1, params.getColumnPos(5));
-        assertEquals("8 key M0 [7]", -1, params.getColumnPos(6));
-        assertEquals("8 key M0 [8]", 2, params.getColumnPos(7));
-        assertEquals("8 key M0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // |[5] [6] [7] [8]
-    // |[1] [2] [3] [4]
-    public void testLayout8KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("8 key L0 columns", 4, params.mNumColumns);
-        assertEquals("8 key L0 rows", 2, params.mNumRows);
-        assertEquals("8 key L0 left", 0, params.mLeftKeys);
-        assertEquals("8 key L0 right", 4, params.mRightKeys);
-        assertEquals("8 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("8 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("8 key L0 [4]", 3, params.getColumnPos(3));
-        assertEquals("8 key L0 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key L0 [6]", 1, params.getColumnPos(5));
-        assertEquals("8 key L0 [7]", 2, params.getColumnPos(6));
-        assertEquals("8 key L0 [8]", 3, params.getColumnPos(7));
-        assertEquals("8 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [5] [6] [7] [8]
-    // |___ [1] [2] [3] [4]
-    public void testLayout8KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("8 key L1 columns", 4, params.mNumColumns);
-        assertEquals("8 key L1 rows", 2, params.mNumRows);
-        assertEquals("8 key L1 left", 0, params.mLeftKeys);
-        assertEquals("8 key L1 right", 4, params.mRightKeys);
-        assertEquals("8 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("8 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("8 key L1 [4]", 3, params.getColumnPos(3));
-        assertEquals("8 key L1 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key L1 [6]", 1, params.getColumnPos(5));
-        assertEquals("8 key L1 [7]", 2, params.getColumnPos(6));
-        assertEquals("8 key L1 [8]", 3, params.getColumnPos(7));
-        assertEquals("8 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [7] [5] [6] [8]
-    // |___ ___ [3] [1] [2] [4]
-    public void testLayout8KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("8 key L2 columns", 4, params.mNumColumns);
-        assertEquals("8 key L2 rows", 2, params.mNumRows);
-        assertEquals("8 key L2 left", 1, params.mLeftKeys);
-        assertEquals("8 key L2 right", 3, params.mRightKeys);
-        assertEquals("8 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("8 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("8 key L2 [4]", 2, params.getColumnPos(3));
-        assertEquals("8 key L2 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key L2 [6]", 1, params.getColumnPos(5));
-        assertEquals("8 key L2 [7]", -1, params.getColumnPos(6));
-        assertEquals("8 key L2 [8]", 2, params.getColumnPos(7));
-        assertEquals("8 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [8] [7] [6] [5]|
-    // [4] [3] [2] [1]|
-    public void testLayout8KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("8 key R0 columns", 4, params.mNumColumns);
-        assertEquals("8 key R0 rows", 2, params.mNumRows);
-        assertEquals("8 key R0 left", 3, params.mLeftKeys);
-        assertEquals("8 key R0 right", 1, params.mRightKeys);
-        assertEquals("8 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("8 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("8 key R0 [4]", -3, params.getColumnPos(3));
-        assertEquals("8 key R0 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key R0 [6]", -1, params.getColumnPos(5));
-        assertEquals("8 key R0 [7]", -2, params.getColumnPos(6));
-        assertEquals("8 key R0 [8]", -3, params.getColumnPos(7));
-        assertEquals("8 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    // [8] [7] [6] [5] ___|
-    // [4] [3] [2] [1] ___|
-    public void testLayout8KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("8 key R1 columns", 4, params.mNumColumns);
-        assertEquals("8 key R1 rows", 2, params.mNumRows);
-        assertEquals("8 key R1 left", 3, params.mLeftKeys);
-        assertEquals("8 key R1 right", 1, params.mRightKeys);
-        assertEquals("8 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("8 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("8 key R1 [4]", -3, params.getColumnPos(3));
-        assertEquals("8 key R1 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key R1 [6]", -1, params.getColumnPos(5));
-        assertEquals("8 key R1 [7]", -2, params.getColumnPos(6));
-        assertEquals("8 key R1 [8]", -3, params.getColumnPos(7));
-        assertEquals("8 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    // [8] [7] [5] [6] ___ ___|
-    // [4] [3] [1] [2] ___ ___|
-    public void testLayout8KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(8, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("8 key R2 columns", 4, params.mNumColumns);
-        assertEquals("8 key R2 rows", 2, params.mNumRows);
-        assertEquals("8 key R2 left", 2, params.mLeftKeys);
-        assertEquals("8 key R2 right", 2, params.mRightKeys);
-        assertEquals("8 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("8 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("8 key R2 [4]", -2, params.getColumnPos(3));
-        assertEquals("8 key R2 [5]", 0, params.getColumnPos(4));
-        assertEquals("8 key R2 [6]", 1, params.getColumnPos(5));
-        assertEquals("8 key R2 [7]", -1, params.getColumnPos(6));
-        assertEquals("8 key R2 [8]", -2, params.getColumnPos(7));
-        assertEquals("8 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("8 key R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    //   [8] [6] [7] [9]
-    // [5] [3] [1] [2] [4]
-    public void testLayout9KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("9 key M0 columns", 5, params.mNumColumns);
-        assertEquals("9 key M0 rows", 2, params.mNumRows);
-        assertEquals("9 key M0 left", 2, params.mLeftKeys);
-        assertEquals("9 key M0 right", 3, params.mRightKeys);
-        assertEquals("9 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key M0 [3]", -1, params.getColumnPos(2));
-        assertEquals("9 key M0 [4]", 2, params.getColumnPos(3));
-        assertEquals("9 key M0 [5]", -2, params.getColumnPos(4));
-        assertEquals("9 key M0 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key M0 [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key M0 [8]", -1, params.getColumnPos(7));
-        assertEquals("9 key M0 [9]", 2, params.getColumnPos(8));
-        assertEquals("9 key M0 adjust", -1, params.mTopRowAdjustment);
-        assertEquals("9 key M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // |[6] [7] [8] [9]
-    // |[1] [2] [3] [4] [5]
-    public void testLayout9KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("9 key L0 columns", 5, params.mNumColumns);
-        assertEquals("9 key L0 rows", 2, params.mNumRows);
-        assertEquals("9 key L0 left", 0, params.mLeftKeys);
-        assertEquals("9 key L0 right", 5, params.mRightKeys);
-        assertEquals("9 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("9 key L0 [4]", 3, params.getColumnPos(3));
-        assertEquals("9 key L0 [5]", 4, params.getColumnPos(4));
-        assertEquals("9 key L0 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key L0 [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key L0 [8]", 2, params.getColumnPos(7));
-        assertEquals("9 key L0 [9]", 3, params.getColumnPos(8));
-        assertEquals("9 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("9 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [6] [7] [8] [9]
-    // |___ [1] [2] [3] [4] [5]
-    public void testLayout9KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("9 key L1 columns", 5, params.mNumColumns);
-        assertEquals("9 key L1 rows", 2, params.mNumRows);
-        assertEquals("9 key L1 left", 0, params.mLeftKeys);
-        assertEquals("9 key L1 right", 5, params.mRightKeys);
-        assertEquals("9 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("9 key L1 [4]", 3, params.getColumnPos(3));
-        assertEquals("9 key L1 [5]", 4, params.getColumnPos(4));
-        assertEquals("9 key L1 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key L1 [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key L1 [8]", 2, params.getColumnPos(7));
-        assertEquals("9 key L1 [9]", 3, params.getColumnPos(8));
-        assertEquals("9 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("9 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___   [8] [6] [7] [9]
-    // |___ ___ [3] [1] [2] [4] [5]
-    public void testLayout9KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("9 key L2 columns", 5, params.mNumColumns);
-        assertEquals("9 key L2 rows", 2, params.mNumRows);
-        assertEquals("9 key L2 left", 1, params.mLeftKeys);
-        assertEquals("9 key L2 right", 4, params.mRightKeys);
-        assertEquals("9 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("9 key L2 [4]", 2, params.getColumnPos(3));
-        assertEquals("9 key L2 [5]", 3, params.getColumnPos(4));
-        assertEquals("9 key L2 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key L2 [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key L2 [8]", -1, params.getColumnPos(7));
-        assertEquals("9 key L2 [9]", 2, params.getColumnPos(8));
-        assertEquals("9 key L2 adjust", 1, params.mTopRowAdjustment);
-        assertEquals("9 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    //     [9] [8] [7] [6]|
-    // [5] [4] [3] [2] [1]|
-    public void testLayout9KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("9 key R0 columns", 5, params.mNumColumns);
-        assertEquals("9 key R0 rows", 2, params.mNumRows);
-        assertEquals("9 key R0 left", 4, params.mLeftKeys);
-        assertEquals("9 key R0 right", 1, params.mRightKeys);
-        assertEquals("9 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("9 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("9 key R0 [4]", -3, params.getColumnPos(3));
-        assertEquals("9 key R0 [5]", -4, params.getColumnPos(4));
-        assertEquals("9 key R0 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key R0 [7]", -1, params.getColumnPos(6));
-        assertEquals("9 key R0 [8]", -2, params.getColumnPos(7));
-        assertEquals("9 key R0 [9]", -3, params.getColumnPos(8));
-        assertEquals("9 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("9 key R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    //     [9] [8] [7] [6] ___|
-    // [5] [4] [3] [2] [1] ___|
-    public void testLayout9KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("9 key R1 columns", 5, params.mNumColumns);
-        assertEquals("9 key R1 rows", 2, params.mNumRows);
-        assertEquals("9 key R1 left", 4, params.mLeftKeys);
-        assertEquals("9 key R1 right", 1, params.mRightKeys);
-        assertEquals("9 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("9 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("9 key R1 [4]", -3, params.getColumnPos(3));
-        assertEquals("9 key R1 [5]", -4, params.getColumnPos(4));
-        assertEquals("9 key R1 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key R1 [7]", -1, params.getColumnPos(6));
-        assertEquals("9 key R1 [8]", -2, params.getColumnPos(7));
-        assertEquals("9 key R1 [9]", -3, params.getColumnPos(8));
-        assertEquals("9 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("9 key R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    //   [9] [8] [6] [7]   ___ ___|
-    // [5] [4] [3] [1] [2] ___ ___|
-    public void testLayout9KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(9, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("9 key R2 columns", 5, params.mNumColumns);
-        assertEquals("9 key R2 rows", 2, params.mNumRows);
-        assertEquals("9 key R2 left", 3, params.mLeftKeys);
-        assertEquals("9 key R2 right", 2, params.mRightKeys);
-        assertEquals("9 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("9 key R2 [4]", -2, params.getColumnPos(3));
-        assertEquals("9 key R2 [5]", -3, params.getColumnPos(4));
-        assertEquals("9 key R2 [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key R2 [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key R2 [8]", -1, params.getColumnPos(7));
-        assertEquals("9 key R2 [9]", -2, params.getColumnPos(8));
-        assertEquals("9 key R2 adjust", -1, params.mTopRowAdjustment);
-        assertEquals("9 key R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    // [A] [8] [6] [7] [9]
-    // [5] [3] [1] [2] [4]
-    public void testLayout10KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("10 key M0 columns", 5, params.mNumColumns);
-        assertEquals("10 key M0 rows", 2, params.mNumRows);
-        assertEquals("10 key M0 left", 2, params.mLeftKeys);
-        assertEquals("10 key M0 right", 3, params.mRightKeys);
-        assertEquals("10 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("10 key M0 [3]", -1, params.getColumnPos(2));
-        assertEquals("10 key M0 [4]", 2, params.getColumnPos(3));
-        assertEquals("10 key M0 [5]", -2, params.getColumnPos(4));
-        assertEquals("10 key M0 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key M0 [7]", 1, params.getColumnPos(6));
-        assertEquals("10 key M0 [8]", -1, params.getColumnPos(7));
-        assertEquals("10 key M0 [9]", 2, params.getColumnPos(8));
-        assertEquals("10 key M0 [A]", -2, params.getColumnPos(9));
-        assertEquals("10 key M0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-
-    // |[6] [7] [8] [9] [A]
-    // |[1] [2] [3] [4] [5]
-    public void testLayout10KeyL0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
-        assertEquals("10 key L0 columns", 5, params.mNumColumns);
-        assertEquals("10 key L0 rows", 2, params.mNumRows);
-        assertEquals("10 key L0 left", 0, params.mLeftKeys);
-        assertEquals("10 key L0 right", 5, params.mRightKeys);
-        assertEquals("10 key L0 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key L0 [2]", 1, params.getColumnPos(1));
-        assertEquals("10 key L0 [3]", 2, params.getColumnPos(2));
-        assertEquals("10 key L0 [4]", 3, params.getColumnPos(3));
-        assertEquals("10 key L0 [5]", 4, params.getColumnPos(4));
-        assertEquals("10 key L0 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key L0 [7]", 1, params.getColumnPos(6));
-        assertEquals("10 key L0 [8]", 2, params.getColumnPos(7));
-        assertEquals("10 key L0 [9]", 3, params.getColumnPos(8));
-        assertEquals("10 key L0 [A]", 4, params.getColumnPos(9));
-        assertEquals("10 key L0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ [6] [7] [8] [9] [A]
-    // |___ [1] [2] [3] [4] [5]
-    public void testLayout10KeyL1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
-        assertEquals("10 key L1 columns", 5, params.mNumColumns);
-        assertEquals("10 key L1 rows", 2, params.mNumRows);
-        assertEquals("10 key L1 left", 0, params.mLeftKeys);
-        assertEquals("10 key L1 right", 5, params.mRightKeys);
-        assertEquals("10 key L1 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key L1 [2]", 1, params.getColumnPos(1));
-        assertEquals("10 key L1 [3]", 2, params.getColumnPos(2));
-        assertEquals("10 key L1 [4]", 3, params.getColumnPos(3));
-        assertEquals("10 key L1 [5]", 4, params.getColumnPos(4));
-        assertEquals("10 key L1 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key L1 [7]", 1, params.getColumnPos(6));
-        assertEquals("10 key L1 [8]", 2, params.getColumnPos(7));
-        assertEquals("10 key L1 [9]", 3, params.getColumnPos(8));
-        assertEquals("10 key L1 [A]", 4, params.getColumnPos(9));
-        assertEquals("10 key L1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
-    }
-
-    // |___ ___ [8] [6] [7] [9] [A]
-    // |___ ___ [3] [1] [2] [4] [5]
-    public void testLayout10KeyL2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
-        assertEquals("10 key L2 columns", 5, params.mNumColumns);
-        assertEquals("10 key L2 rows", 2, params.mNumRows);
-        assertEquals("10 key L2 left", 1, params.mLeftKeys);
-        assertEquals("10 key L2 right", 4, params.mRightKeys);
-        assertEquals("10 key L2 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key L2 [2]", 1, params.getColumnPos(1));
-        assertEquals("10 key L2 [3]", -1, params.getColumnPos(2));
-        assertEquals("10 key L2 [4]", 2, params.getColumnPos(3));
-        assertEquals("10 key L2 [5]", 3, params.getColumnPos(4));
-        assertEquals("10 key L2 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key L2 [7]", 1, params.getColumnPos(6));
-        assertEquals("10 key L2 [8]", -1, params.getColumnPos(7));
-        assertEquals("10 key L2 [9]", 2, params.getColumnPos(8));
-        assertEquals("10 key L2 [A]", 3, params.getColumnPos(9));
-        assertEquals("10 key L2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [A] [9] [8] [7] [6]|
-    // [5] [4] [3] [2] [1]|
-    public void testLayout10KeyR0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
-        assertEquals("10 key R0 columns", 5, params.mNumColumns);
-        assertEquals("10 key R0 rows", 2, params.mNumRows);
-        assertEquals("10 key R0 left", 4, params.mLeftKeys);
-        assertEquals("10 key R0 right", 1, params.mRightKeys);
-        assertEquals("10 key R0 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key R0 [2]", -1, params.getColumnPos(1));
-        assertEquals("10 key R0 [3]", -2, params.getColumnPos(2));
-        assertEquals("10 key R0 [4]", -3, params.getColumnPos(3));
-        assertEquals("10 key R0 [5]", -4, params.getColumnPos(4));
-        assertEquals("10 key R0 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key R0 [7]", -1, params.getColumnPos(6));
-        assertEquals("10 key R0 [8]", -2, params.getColumnPos(7));
-        assertEquals("10 key R0 [9]", -3, params.getColumnPos(8));
-        assertEquals("10 key R0 [A]", -4, params.getColumnPos(9));
-        assertEquals("10 key R0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    // [A] [9] [8] [7] [6] ___|
-    // [5] [4] [3] [2] [1] ___|
-    public void testLayout10KeyR1() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
-        assertEquals("10 key R1 columns", 5, params.mNumColumns);
-        assertEquals("10 key R1 rows", 2, params.mNumRows);
-        assertEquals("10 key R1 left", 4, params.mLeftKeys);
-        assertEquals("10 key R1 right", 1, params.mRightKeys);
-        assertEquals("10 key R1 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key R1 [2]", -1, params.getColumnPos(1));
-        assertEquals("10 key R1 [3]", -2, params.getColumnPos(2));
-        assertEquals("10 key R1 [4]", -3, params.getColumnPos(3));
-        assertEquals("10 key R1 [5]", -4, params.getColumnPos(4));
-        assertEquals("10 key R1 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key R1 [7]", -1, params.getColumnPos(6));
-        assertEquals("10 key R1 [8]", -2, params.getColumnPos(7));
-        assertEquals("10 key R1 [9]", -3, params.getColumnPos(8));
-        assertEquals("10 key R1 [A]", -4, params.getColumnPos(9));
-        assertEquals("10 key R1 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    // [A] [9] [8] [6] [7] ___ ___|
-    // [5] [4] [3] [1] [2] ___ ___|
-    public void testLayout10KeyR2() {
-        MiniKeyboardParams params = new MiniKeyboardParams(10, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
-        assertEquals("10 key R2 columns", 5, params.mNumColumns);
-        assertEquals("10 key R2 rows", 2, params.mNumRows);
-        assertEquals("10 key R2 left", 3, params.mLeftKeys);
-        assertEquals("10 key R2 right", 2, params.mRightKeys);
-        assertEquals("10 key R2 [1]", 0, params.getColumnPos(0));
-        assertEquals("10 key R2 [2]", 1, params.getColumnPos(1));
-        assertEquals("10 key R2 [3]", -1, params.getColumnPos(2));
-        assertEquals("10 key R2 [4]", -2, params.getColumnPos(3));
-        assertEquals("10 key R2 [5]", -3, params.getColumnPos(4));
-        assertEquals("10 key R2 [6]", 0, params.getColumnPos(5));
-        assertEquals("10 key R2 [7]", 1, params.getColumnPos(6));
-        assertEquals("10 key R2 [8]", -1, params.getColumnPos(7));
-        assertEquals("10 key R2 [9]", -2, params.getColumnPos(8));
-        assertEquals("10 key R2 [A]", -3, params.getColumnPos(9));
-        assertEquals("10 key R2 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("10 key R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
-    }
-
-    //   [B] [9] [A]
-    // [7] [5] [6] [8]
-    // [3] [1] [2] [4]
-    public void testLayout11KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(11, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("11 key M0 columns", 4, params.mNumColumns);
-        assertEquals("11 key M0 rows", 3, params.mNumRows);
-        assertEquals("11 key M0 left", 1, params.mLeftKeys);
-        assertEquals("11 key M0 right", 3, params.mRightKeys);
-        assertEquals("11 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("11 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("11 key M0 [3]", -1, params.getColumnPos(2));
-        assertEquals("11 key M0 [4]", 2, params.getColumnPos(3));
-        assertEquals("11 key M0 [5]", 0, params.getColumnPos(4));
-        assertEquals("11 key M0 [6]", 1, params.getColumnPos(5));
-        assertEquals("11 key M0 [7]", -1, params.getColumnPos(6));
-        assertEquals("11 key M0 [8]", 2, params.getColumnPos(7));
-        assertEquals("11 key M0 [9]", 0, params.getColumnPos(8));
-        assertEquals("11 key M0 [A]", 1, params.getColumnPos(9));
-        assertEquals("11 key M0 [B]", -1, params.getColumnPos(10));
-        assertEquals("11 key M0 adjust", 1, params.mTopRowAdjustment);
-        assertEquals("11 key M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-    // [B] [9] [A] [C]
-    // [7] [5] [6] [8]
-    // [3] [1] [2] [4]
-    public void testLayout12KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(12, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("12 key M0 columns", 4, params.mNumColumns);
-        assertEquals("12 key M0 rows", 3, params.mNumRows);
-        assertEquals("12 key M0 left", 1, params.mLeftKeys);
-        assertEquals("12 key M0 right", 3, params.mRightKeys);
-        assertEquals("12 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("12 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("12 key M0 [3]", -1, params.getColumnPos(2));
-        assertEquals("12 key M0 [4]", 2, params.getColumnPos(3));
-        assertEquals("12 key M0 [5]", 0, params.getColumnPos(4));
-        assertEquals("12 key M0 [6]", 1, params.getColumnPos(5));
-        assertEquals("12 key M0 [7]", -1, params.getColumnPos(6));
-        assertEquals("12 key M0 [8]", 2, params.getColumnPos(7));
-        assertEquals("12 key M0 [9]", 0, params.getColumnPos(8));
-        assertEquals("12 key M0 [A]", 1, params.getColumnPos(9));
-        assertEquals("12 key M0 [B]", -1, params.getColumnPos(10));
-        assertEquals("12 key M0 [C]", 2, params.getColumnPos(11));
-        assertEquals("12 key M0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("12 key M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
-    }
-
-
-    //     [D] [B] [C]
-    // [A] [8] [6] [7] [9]
-    // [5] [3] [1] [2] [4]
-    public void testLayout13KeyM0() {
-        MiniKeyboardParams params = new MiniKeyboardParams(13, MAX_COLUMNS, WIDTH,
-                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
-        assertEquals("13 key M0 columns", 5, params.mNumColumns);
-        assertEquals("13 key M0 rows", 3, params.mNumRows);
-        assertEquals("13 key M0 left", 2, params.mLeftKeys);
-        assertEquals("13 key M0 right", 3, params.mRightKeys);
-        assertEquals("13 key M0 [1]", 0, params.getColumnPos(0));
-        assertEquals("13 key M0 [2]", 1, params.getColumnPos(1));
-        assertEquals("13 key M0 [3]", -1, params.getColumnPos(2));
-        assertEquals("13 key M0 [4]", 2, params.getColumnPos(3));
-        assertEquals("13 key M0 [5]", -2, params.getColumnPos(4));
-        assertEquals("13 key M0 [6]", 0, params.getColumnPos(5));
-        assertEquals("13 key M0 [7]", 1, params.getColumnPos(6));
-        assertEquals("13 key M0 [8]", -1, params.getColumnPos(7));
-        assertEquals("13 key M0 [9]", 2, params.getColumnPos(8));
-        assertEquals("13 key M0 [A]", -2, params.getColumnPos(9));
-        assertEquals("13 key M0 [B]", 0, params.getColumnPos(10));
-        assertEquals("13 key M0 [C]", 1, params.getColumnPos(11));
-        assertEquals("13 key M0 [D]", -1, params.getColumnPos(12));
-        assertEquals("13 key M0 adjust", 0, params.mTopRowAdjustment);
-        assertEquals("13 key M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
-    }
-}
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
new file mode 100644
index 0000000..5c6c834
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -0,0 +1,2631 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams;
+
+public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase {
+    private static final int WIDTH = 10;
+    private static final int HEIGHT = 10;
+
+    private static final int KEYBOARD_WIDTH = WIDTH * 10;
+    private static final int XPOS_L0 = WIDTH * 0 + WIDTH / 2;
+    private static final int XPOS_L1 = WIDTH * 1 + WIDTH / 2;
+    private static final int XPOS_L2 = WIDTH * 2 + WIDTH / 2;
+    private static final int XPOS_L3 = WIDTH * 3 + WIDTH / 2;
+    private static final int XPOS_M0 = WIDTH * 4 + WIDTH / 2;
+    private static final int XPOS_M1 = WIDTH * 5 + WIDTH / 2;
+    private static final int XPOS_R3 = WIDTH * 6 + WIDTH / 2;
+    private static final int XPOS_R2 = WIDTH * 7 + WIDTH / 2;
+    private static final int XPOS_R1 = WIDTH * 8 + WIDTH / 2;
+    private static final int XPOS_R0 = WIDTH * 9 + WIDTH / 2;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    private static MoreKeysKeyboardParams createParams(int numKeys, int columnNum,
+            int coordXInParnet) {
+        final MoreKeysKeyboardParams params = new MoreKeysKeyboardParams();
+        params.setParameters(numKeys, columnNum, WIDTH, HEIGHT, coordXInParnet, KEYBOARD_WIDTH,
+                /* isFixedOrderColumn */true, /* dividerWidth */0);
+        return params;
+    }
+
+    public void testLayoutError() {
+        MoreKeysKeyboardParams params = null;
+        try {
+            final int fixColumns = KEYBOARD_WIDTH / WIDTH;
+            params = createParams(10, fixColumns + 1, HEIGHT);
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Too small keyboard to hold more keys keyboard.
+        }
+        assertNull("Too small keyboard to hold more keys keyboard", params);
+    }
+
+    // More keys keyboard layout test.
+    // "[n]" represents n-th key position in more keys keyboard.
+    // "<m>" is the default key.
+
+    // <1>
+    public void testLayout1KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_M0);
+        assertEquals("1 key fix 5 M0 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 M0 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 M0 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 M0 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |<1>
+    public void testLayout1KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_L0);
+        assertEquals("1 key fix 5 L0 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 L0 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 L0 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1>
+    public void testLayout1KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_L1);
+        assertEquals("1 key fix 5 L1 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 L1 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 L1 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ <1>
+    public void testLayout1KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_L2);
+        assertEquals("1 key fix 5 L2 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 L2 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 L2 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 L2 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1>|
+    public void testLayout1KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_R0);
+        assertEquals("1 key fix 5 R0 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 R0 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 R0 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 R0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1> ___|
+    public void testLayout1KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_R1);
+        assertEquals("1 key fix 5 R1 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 R1 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 R1 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 R1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1> ___ ___|
+    public void testLayout1KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_R2);
+        assertEquals("1 key fix 5 R2 columns", 1, params.mNumColumns);
+        assertEquals("1 key fix 5 R2 rows", 1, params.mNumRows);
+        assertEquals("1 key fix 5 R2 left", 0, params.mLeftKeys);
+        assertEquals("1 key fix 5 R2 right", 1, params.mRightKeys);
+        assertEquals("1 key fix 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key fix 5 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1> [2]
+    public void testLayout2KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_M0);
+        assertEquals("2 key fix 5 M0 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 M0 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 M0 left", 0, params.mLeftKeys);
+        assertEquals("2 key fix 5 M0 right", 2, params.mRightKeys);
+        assertEquals("2 key fix 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key fix 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key fix 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2]
+    public void testLayout2KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_L0);
+        assertEquals("2 key fix 5 L0 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 L0 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("2 key fix 5 L0 right", 2, params.mRightKeys);
+        assertEquals("2 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2]
+    public void testLayout2KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_L1);
+        assertEquals("2 key fix 5 L1 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 L1 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("2 key fix 5 L1 right", 2, params.mRightKeys);
+        assertEquals("2 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ <1> [2]
+    public void testLayout2KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_L2);
+        assertEquals("2 key fix 5 L2 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 L2 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 L2 left", 0, params.mLeftKeys);
+        assertEquals("2 key fix 5 L2 right", 2, params.mRightKeys);
+        assertEquals("2 key fix 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key fix 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [1] <2>|
+    public void testLayout2KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_R0);
+        assertEquals("2 key fix 5 R0 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 R0 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 R0 left", 1, params.mLeftKeys);
+        assertEquals("2 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("2 key fix 5 R0 [1]", -1, params.getColumnPos(0));
+        assertEquals("2 key fix 5 R0 <2>", 0, params.getColumnPos(1));
+        assertEquals("2 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [1] <2> ___|
+    public void testLayout2KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_R1);
+        assertEquals("2 key fix 5 R1 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 R1 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 R1 left", 1, params.mLeftKeys);
+        assertEquals("2 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("2 key fix 5 R1 [1]", -1, params.getColumnPos(0));
+        assertEquals("2 key fix 5 R1 <2>", 0, params.getColumnPos(1));
+        assertEquals("2 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // <1> [2] ___|
+    public void testLayout2KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_R2);
+        assertEquals("2 key fix 5 R2 columns", 2, params.mNumColumns);
+        assertEquals("2 key fix 5 R2 rows", 1, params.mNumRows);
+        assertEquals("2 key fix 5 R2 left", 0, params.mLeftKeys);
+        assertEquals("2 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("2 key fix 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key fix 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key fix 5 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [3]
+    // <1> [2]
+    public void testLayout3KeyFix2M0() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_M0);
+        assertEquals("3 key fix 2 M0 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 M0 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 M0 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 M0 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 M0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |[3]
+    // |<1> [2]
+    public void testLayout3KeyFix2L0() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_L0);
+        assertEquals("3 key fix 2 L0 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 L0 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 L0 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 L0 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 L0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3]
+    // |___ <1> [2]
+    public void testLayout3KeyFix2L1() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_L1);
+        assertEquals("3 key fix 2 L1 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 L1 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 L1 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 L1 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 L1 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |        [3]
+    // |___ ___ <1> [2]
+    public void testLayout3KeyFix2L2() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_L2);
+        assertEquals("3 key fix 2 L2 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 L2 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 L2 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 L2 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 L2 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    //     [3]|
+    // [1] <2>|
+    public void testLayout3KeyFix2R0() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_R0);
+        assertEquals("3 key fix 2 R0 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 R0 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 R0 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 2 R0 right", 1, params.mRightKeys);
+        assertEquals("3 key fix 2 R0 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 2 R0 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 2 R0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [3] ___|
+    // [1] <2> ___|
+    public void testLayout3KeyFix2R1() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_R1);
+        assertEquals("3 key fix 2 R1 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 R1 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 R1 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 2 R1 right", 1, params.mRightKeys);
+        assertEquals("3 key fix 2 R1 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 2 R1 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 2 R1 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3]     ___|
+    // <1> [2] ___|
+    public void testLayout3KeyFix2R2() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_R2);
+        assertEquals("3 key fix 2 R2 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 R2 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 R2 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 R2 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 R2 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [4]
+    // <1> [2]
+    public void testLayout4KeyFix2M0() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_M0);
+        assertEquals("3 key fix 2 M0 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 M0 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 M0 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 M0 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 M0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("3 key fix 2 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |[3] [4]
+    // |<1> [2]
+    public void testLayout4KeyFix2L0() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_L0);
+        assertEquals("3 key fix 2 L0 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 L0 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 L0 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 L0 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 L0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 L0 [4]", 1, params.getColumnPos(3));
+        assertEquals("3 key fix 2 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] [4]
+    // |___ <1> [2]
+    public void testLayout4KeyFix2L1() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_L1);
+        assertEquals("3 key fix 2 L1 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 L1 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 L1 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 L1 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 L1 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 L1 [4]", 1, params.getColumnPos(3));
+        assertEquals("3 key fix 2 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |        [3] [4]
+    // |___ ___ <1> [2]
+    public void testLayout4KeyFix2L2() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_L2);
+        assertEquals("3 key fix 2 L2 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 L2 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 L2 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 L2 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 L2 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 L2 [4]", 1, params.getColumnPos(3));
+        assertEquals("3 key fix 2 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [4]|
+    // [1] <2>|
+    public void testLayout4KeyFix2R0() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_R0);
+        assertEquals("3 key fix 2 R0 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 R0 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 R0 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 2 R0 right", 1, params.mRightKeys);
+        assertEquals("3 key fix 2 R0 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 2 R0 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 2 R0 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key fix 2 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("3 key fix 2 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [4] ___|
+    // [1] <2> ___|
+    public void testLayout4KeyFix2R1() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_R1);
+        assertEquals("3 key fix 2 R1 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 R1 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 R1 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 2 R1 right", 1, params.mRightKeys);
+        assertEquals("3 key fix 2 R1 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 2 R1 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 2 R1 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key fix 2 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("3 key fix 2 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [4] ___|
+    // <1> [2] ___|
+    public void testLayout4KeyFix2R2() {
+        MoreKeysKeyboardParams params = createParams(4, 2, XPOS_R2);
+        assertEquals("3 key fix 2 R2 columns", 2, params.mNumColumns);
+        assertEquals("3 key fix 2 R2 rows", 2, params.mNumRows);
+        assertEquals("3 key fix 2 R2 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 2 R2 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 2 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 2 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 2 R2 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 2 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("3 key fix 2 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 2 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [1] <2> [3]
+    public void testLayout3KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_M0);
+        assertEquals("3 key fix 5 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 5 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 5 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 5 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 5 [3]", 1, params.getColumnPos(2));
+        assertEquals("3 key fix 5 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3]
+    public void testLayout3KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_L0);
+        assertEquals("3 key fix 5 L0 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 L0 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 5 L0 right", 3, params.mRightKeys);
+        assertEquals("3 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("3 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3]
+    public void testLayout3KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_L1);
+        assertEquals("3 key fix 5 L1 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 L1 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("3 key fix 5 L1 right", 3, params.mRightKeys);
+        assertEquals("3 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("3 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] <2> [3]
+    public void testLayout3KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_L2);
+        assertEquals("3 key fix 5 L2 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 L2 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 5 L2 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("3 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] <3>|
+    public void testLayout3KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_R0);
+        assertEquals("3 key fix 5 R0 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 R0 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 R0 left", 2, params.mLeftKeys);
+        assertEquals("3 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("3 key fix 5 R0 [1]", -2, params.getColumnPos(0));
+        assertEquals("3 key fix 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key fix 5 R0 <3>", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] <3> ___|
+    public void testLayout3KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_R1);
+        assertEquals("3 key fix 5 R1 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 R1 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 R1 left", 2, params.mLeftKeys);
+        assertEquals("3 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("3 key fix 5 R1 [1]", -2, params.getColumnPos(0));
+        assertEquals("3 key fix 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key fix 5 R1 <3>", 0, params.getColumnPos(2));
+        assertEquals("3 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [1] <2> [3] ___|
+    public void testLayout3KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_R2);
+        assertEquals("3 key fix 5 R2 columns", 3, params.mNumColumns);
+        assertEquals("3 key fix 5 R2 rows", 1, params.mNumRows);
+        assertEquals("3 key fix 5 R2 left", 1, params.mLeftKeys);
+        assertEquals("3 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("3 key fix 5 R2 [1]", -1, params.getColumnPos(0));
+        assertEquals("3 key fix 5 R2 <2>", 0, params.getColumnPos(1));
+        assertEquals("3 key fix 5 R2 [3]", 1, params.getColumnPos(2));
+        assertEquals("3 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key fix 5 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [4]
+    // [1] <2> [3]
+    public void testLayout4KeyFix3M0() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_M0);
+        assertEquals("4 key fix 3 M0 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 M0 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 M0 left", 1, params.mLeftKeys);
+        assertEquals("4 key fix 3 M0 right", 2, params.mRightKeys);
+        assertEquals("4 key fix 3 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("4 key fix 3 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("4 key fix 3 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("4 key fix 3 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4]
+    // |<1> [2] [3]
+    public void testLayout4KeyFix3L0() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_L0);
+        assertEquals("4 key fix 3 L0 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 L0 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 L0 left", 0, params.mLeftKeys);
+        assertEquals("4 key fix 3 L0 right", 3, params.mRightKeys);
+        assertEquals("4 key fix 3 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key fix 3 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key fix 3 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key fix 3 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4]
+    // |___ <1> [2] [3]
+    public void testLayout4KeyFix3L1() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_L1);
+        assertEquals("4 key fix 3 L1 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 L1 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 L1 left", 0, params.mLeftKeys);
+        assertEquals("4 key fix 3 L1 right", 3, params.mRightKeys);
+        assertEquals("4 key fix 3 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key fix 3 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key fix 3 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key fix 3 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___     [4]
+    // |___ ___ [1] <2> [3]
+    public void testLayout4KeyFix3L2() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_L2);
+        assertEquals("4 key fix 3 L2 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 L2 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 L2 left", 1, params.mLeftKeys);
+        assertEquals("4 key fix 3 L2 right", 2, params.mRightKeys);
+        assertEquals("4 key fix 3 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("4 key fix 3 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("4 key fix 3 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("4 key fix 3 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //         [4]|
+    // [1] [2] <3>|
+    public void testLayout4KeyFix3R0() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_R0);
+        assertEquals("4 key fix 3 R0 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 R0 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 R0 left", 2, params.mLeftKeys);
+        assertEquals("4 key fix 3 R0 right", 1, params.mRightKeys);
+        assertEquals("4 key fix 3 R0 [1]", -2, params.getColumnPos(0));
+        assertEquals("4 key fix 3 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key fix 3 R0 <3>", 0, params.getColumnPos(2));
+        assertEquals("4 key fix 3 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //         [4] ___|
+    // [1] [2] <3> ___|
+    public void testLayout4KeyFix3R1() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_R1);
+        assertEquals("4 key fix 3 R1 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 R1 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 R1 left", 2, params.mLeftKeys);
+        assertEquals("4 key fix 3 R1 right", 1, params.mRightKeys);
+        assertEquals("4 key fix 3 R1 [1]", -2, params.getColumnPos(0));
+        assertEquals("4 key fix 3 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key fix 3 R1 <3>", 0, params.getColumnPos(2));
+        assertEquals("4 key fix 3 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [4]     ___|
+    // [1] <2> [3] ___|
+    public void testLayout4KeyFix3R2() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_R2);
+        assertEquals("4 key fix 3 R2 columns", 3, params.mNumColumns);
+        assertEquals("4 key fix 3 R2 rows", 2, params.mNumRows);
+        assertEquals("4 key fix 3 R2 left", 1, params.mLeftKeys);
+        assertEquals("4 key fix 3 R2 right", 2, params.mRightKeys);
+        assertEquals("4 key fix 3 R2 [1]", -1, params.getColumnPos(0));
+        assertEquals("4 key fix 3 R2 <2>", 0, params.getColumnPos(1));
+        assertEquals("4 key fix 3 R2 [3]", 1, params.getColumnPos(2));
+        assertEquals("4 key fix 3 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 3 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 3 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //   [4] [5]
+    // [1] <2> [3]
+    public void testLayout5KeyFix3M0() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_M0);
+        assertEquals("5 key fix 3 M0 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 M0 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 M0 left", 1, params.mLeftKeys);
+        assertEquals("5 key fix 3 M0 right", 2, params.mRightKeys);
+        assertEquals("5 key fix 3 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("5 key fix 3 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("5 key fix 3 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("5 key fix 3 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 3 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key fix 3 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4] [5]
+    // |<1> [2] [3]
+    public void testLayout5KeyFix3L0() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_L0);
+        assertEquals("5 key fix 3 L0 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 L0 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key fix 3 L0 right", 3, params.mRightKeys);
+        assertEquals("5 key fix 3 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key fix 3 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key fix 3 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key fix 3 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 3 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key fix 3 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5]
+    // |___ <1> [2] [3]
+    public void testLayout5KeyFix3L1() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_L1);
+        assertEquals("5 key fix 3 L1 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 L1 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key fix 3 L1 right", 3, params.mRightKeys);
+        assertEquals("5 key fix 3 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key fix 3 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key fix 3 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key fix 3 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 3 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key fix 3 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [4] [5]
+    // |___ [1] <2> [3]
+    public void testLayout5KeyFix3L2() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_L2);
+        assertEquals("5 key fix 3 L2 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 L2 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key fix 3 L2 right", 2, params.mRightKeys);
+        assertEquals("5 key fix 3 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("5 key fix 3 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("5 key fix 3 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("5 key fix 3 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 3 L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key fix 3 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [4] [5]|
+    // [1] [2] <3>|
+    public void testLayout5KeyFix3R0() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_R0);
+        assertEquals("5 key fix 3 R0 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 R0 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 R0 left", 2, params.mLeftKeys);
+        assertEquals("5 key fix 3 R0 right", 1, params.mRightKeys);
+        assertEquals("5 key fix 3 R0 [1]", -2, params.getColumnPos(0));
+        assertEquals("5 key fix 3 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key fix 3 R0 <3>", 0, params.getColumnPos(2));
+        assertEquals("5 key fix 3 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("5 key fix 3 R0 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 3 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [4] [5] ___|
+    // [1] [2] <3> ___|
+    public void testLayout5KeyFix3R1() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_R1);
+        assertEquals("5 key fix 3 R1 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 R1 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 R1 left", 2, params.mLeftKeys);
+        assertEquals("5 key fix 3 R1 right", 1, params.mRightKeys);
+        assertEquals("5 key fix 3 R1 [1]", -2, params.getColumnPos(0));
+        assertEquals("5 key fix 3 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key fix 3 R1 <3>", 0, params.getColumnPos(2));
+        assertEquals("5 key fix 3 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("5 key fix 3 R1 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 3 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [4] [5]   ___|
+    // [1] <2> [3] ___|
+    public void testLayout5KeyFix3R2() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_R2);
+        assertEquals("5 key fix 3 R2 columns", 3, params.mNumColumns);
+        assertEquals("5 key fix 3 R2 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 3 R2 left", 1, params.mLeftKeys);
+        assertEquals("5 key fix 3 R2 right", 2, params.mRightKeys);
+        assertEquals("5 key fix 3 R2 [1]", -1, params.getColumnPos(0));
+        assertEquals("5 key fix 3 R2 <2>", 0, params.getColumnPos(1));
+        assertEquals("5 key fix 3 R2 [3]", 1, params.getColumnPos(2));
+        assertEquals("5 key fix 3 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 3 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key fix 3 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key fix 3 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [5] [6]
+    // [1] <2> [3]
+    public void testLayout6KeyFix3M0() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_M0);
+        assertEquals("6 key fix 3 M0 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 M0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 M0 left", 1, params.mLeftKeys);
+        assertEquals("6 key fix 3 M0 right", 2, params.mRightKeys);
+        assertEquals("6 key fix 3 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("6 key fix 3 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("6 key fix 3 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("6 key fix 3 M0 [4]", -1, params.getColumnPos(3));
+        assertEquals("6 key fix 3 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 3 M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 3 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4] [5] [6]
+    // |<1> [2] [3]
+    public void testLayout6KeyFix3L0() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_L0);
+        assertEquals("6 key fix 3 L0 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 L0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 L0 left", 0, params.mLeftKeys);
+        assertEquals("6 key fix 3 L0 right", 3, params.mRightKeys);
+        assertEquals("6 key fix 3 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key fix 3 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key fix 3 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key fix 3 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key fix 3 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key fix 3 L0 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key fix 3 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5] [6]
+    // |___ <1> [2] [3]
+    public void testLayout6KeyFix3L1() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_L1);
+        assertEquals("6 key fix 3 L1 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 L1 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 L1 left", 0, params.mLeftKeys);
+        assertEquals("6 key fix 3 L1 right", 3, params.mRightKeys);
+        assertEquals("6 key fix 3 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key fix 3 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key fix 3 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key fix 3 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key fix 3 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key fix 3 L1 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key fix 3 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5] [6]
+    // |___ [1] <2> [3]
+    public void testLayout6KeyFix3L2() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_L2);
+        assertEquals("6 key fix 3 L2 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 L2 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 L2 left", 1, params.mLeftKeys);
+        assertEquals("6 key fix 3 L2 right", 2, params.mRightKeys);
+        assertEquals("6 key fix 3 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("6 key fix 3 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("6 key fix 3 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("6 key fix 3 L2 [4]", -1, params.getColumnPos(3));
+        assertEquals("6 key fix 3 L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 3 L2 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 3 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [5] [6]|
+    // [1] [2] <3>|
+    public void testLayout6KeyFix3R0() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_R0);
+        assertEquals("6 key fix 3 R0 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 R0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 R0 left", 2, params.mLeftKeys);
+        assertEquals("6 key fix 3 R0 right", 1, params.mRightKeys);
+        assertEquals("6 key fix 3 R0 [1]", -2, params.getColumnPos(0));
+        assertEquals("6 key fix 3 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key fix 3 R0 <3>", 0, params.getColumnPos(2));
+        assertEquals("6 key fix 3 R0 [4]", -2, params.getColumnPos(3));
+        assertEquals("6 key fix 3 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key fix 3 R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 3 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [5] [6] ___|
+    // [1] [2] <3> ___|
+    public void testLayout6KeyFix3R1() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_R1);
+        assertEquals("6 key fix 3 R1 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 R1 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 R1 left", 2, params.mLeftKeys);
+        assertEquals("6 key fix 3 R1 right", 1, params.mRightKeys);
+        assertEquals("6 key fix 3 R1 [1]", -2, params.getColumnPos(0));
+        assertEquals("6 key fix 3 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key fix 3 R1 <3>", 0, params.getColumnPos(2));
+        assertEquals("6 key fix 3 R1 [4]", -2, params.getColumnPos(3));
+        assertEquals("6 key fix 3 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key fix 3 R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 3 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [5] [6] ___|
+    // [1] <2> [3] ___|
+    public void testLayout6KeyFix3R2() {
+        MoreKeysKeyboardParams params = createParams(6, 3, XPOS_R2);
+        assertEquals("6 key fix 3 R2 columns", 3, params.mNumColumns);
+        assertEquals("6 key fix 3 R2 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 3 R2 left", 1, params.mLeftKeys);
+        assertEquals("6 key fix 3 R2 right", 2, params.mRightKeys);
+        assertEquals("6 key fix 3 R2 [1]", -1, params.getColumnPos(0));
+        assertEquals("6 key fix 3 R2 <2>", 0, params.getColumnPos(1));
+        assertEquals("6 key fix 3 R2 [1]", 1, params.getColumnPos(2));
+        assertEquals("6 key fix 3 R2 [4]", -1, params.getColumnPos(3));
+        assertEquals("6 key fix 3 R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 3 R2 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 3 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 3 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // <1> [2] [3] [4]
+    public void testLayout4KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_M0);
+        assertEquals("4 key fix 5 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 left", 1, params.mLeftKeys);
+        assertEquals("4 key fix 5 right", 3, params.mRightKeys);
+        assertEquals("4 key fix 5 <1>", -1, params.getColumnPos(0));
+        assertEquals("4 key fix 5 [2]", 0, params.getColumnPos(1));
+        assertEquals("4 key fix 5 [3]", 1, params.getColumnPos(2));
+        assertEquals("4 key fix 5 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key fix 5 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4]
+    public void testLayout4KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_L0);
+        assertEquals("4 key fix 5 L0 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 L0 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("4 key fix 5 L0 right", 4, params.mRightKeys);
+        assertEquals("4 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4]
+    public void testLayout4KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_L1);
+        assertEquals("4 key fix 5 L1 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 L1 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("4 key fix 5 L1 right", 4, params.mRightKeys);
+        assertEquals("4 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] <2> [3] [4]
+    public void testLayout4KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_L2);
+        assertEquals("4 key fix 5 L2 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 L2 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("4 key fix 5 L2 right", 3, params.mRightKeys);
+        assertEquals("4 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("4 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("4 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("4 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] <4>|
+    public void testLayout4KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_R0);
+        assertEquals("4 key fix 5 R0 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 R0 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 R0 left", 3, params.mLeftKeys);
+        assertEquals("4 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("4 key fix 5 R0 [1]", -3, params.getColumnPos(0));
+        assertEquals("4 key fix 5 R0 [2]", -2, params.getColumnPos(1));
+        assertEquals("4 key fix 5 R0 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key fix 5 R0 <4>", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] <4> ___|
+    public void testLayout4KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_R1);
+        assertEquals("4 key fix 5 R1 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 R1 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 R1 left", 3, params.mLeftKeys);
+        assertEquals("4 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("4 key fix 5 R1 [1]", -3, params.getColumnPos(0));
+        assertEquals("4 key fix 5 R1 [2]", -2, params.getColumnPos(1));
+        assertEquals("4 key fix 5 R1 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key fix 5 R1 <4>", 0, params.getColumnPos(3));
+        assertEquals("4 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] <3> [4] ___|
+    public void testLayout4KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_R2);
+        assertEquals("4 key fix 5 R2 columns", 4, params.mNumColumns);
+        assertEquals("4 key fix 5 R2 rows", 1, params.mNumRows);
+        assertEquals("4 key fix 5 R2 left", 2, params.mLeftKeys);
+        assertEquals("4 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("4 key fix 5 R2 [1]", -2, params.getColumnPos(0));
+        assertEquals("4 key fix 5 R2 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key fix 5 R2 <3>", 0, params.getColumnPos(2));
+        assertEquals("4 key fix 5 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("4 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key fix 5 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [5]
+    // [1] <2> [3] [4]
+    public void testLayout5KeyFix4M0() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_M0);
+        assertEquals("5 key fix 4 M0 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 M0 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("5 key fix 4 M0 right", 3, params.mRightKeys);
+        assertEquals("5 key fix 4 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("5 key fix 4 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("5 key fix 4 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("5 key fix 4 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("5 key fix 4 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5]
+    // |<1> [2] [3] [4]
+    public void testLayout5KeyFix4L0() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_L0);
+        assertEquals("5 key fix 4 L0 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 L0 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key fix 4 L0 right", 4, params.mRightKeys);
+        assertEquals("5 key fix 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key fix 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key fix 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key fix 4 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key fix 4 L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5]
+    // |___ <1> [2] [3] [4]
+    public void testLayout5KeyFix4L1() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_L1);
+        assertEquals("5 key fix 4 L1 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 L1 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key fix 4 L1 right", 4, params.mRightKeys);
+        assertEquals("5 key fix 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key fix 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key fix 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key fix 4 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key fix 4 L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___     [5]
+    // |___ [1] <2> [3] [4]
+    public void testLayout5KeyFix4L2() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_L2);
+        assertEquals("5 key fix 4 L2 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 L2 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key fix 4 L2 right", 3, params.mRightKeys);
+        assertEquals("5 key fix 4 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("5 key fix 4 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("5 key fix 4 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("5 key fix 4 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("5 key fix 4 L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //             [5]|
+    // [1] [2] [3] <4>|
+    public void testLayout5KeyFix4R0() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_R0);
+        assertEquals("5 key fix 4 R0 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 R0 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 R0 left", 3, params.mLeftKeys);
+        assertEquals("5 key fix 4 R0 right", 1, params.mRightKeys);
+        assertEquals("5 key fix 4 R0 [1]", -3, params.getColumnPos(0));
+        assertEquals("5 key fix 4 R0 [2]", -2, params.getColumnPos(1));
+        assertEquals("5 key fix 4 R0 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key fix 4 R0 <4>", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 4 R0 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //             [5] ___|
+    // [1] [2] [3] <4> ___|
+    public void testLayout5KeyFix4R1() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_R1);
+        assertEquals("5 key fix 4 R1 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 R1 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 R1 left", 3, params.mLeftKeys);
+        assertEquals("5 key fix 4 R1 right", 1, params.mRightKeys);
+        assertEquals("5 key fix 4 R1 [1]", -3, params.getColumnPos(0));
+        assertEquals("5 key fix 4 R1 [2]", -2, params.getColumnPos(1));
+        assertEquals("5 key fix 4 R1 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key fix 4 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 4 R1 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //         [5]     ___|
+    // [1] [2] <3> [4] ___|
+    public void testLayout5KeyFix4R2() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_R2);
+        assertEquals("5 key fix 4 R2 columns", 4, params.mNumColumns);
+        assertEquals("5 key fix 4 R2 rows", 2, params.mNumRows);
+        assertEquals("5 key fix 4 R2 left", 2, params.mLeftKeys);
+        assertEquals("5 key fix 4 R2 right", 2, params.mRightKeys);
+        assertEquals("5 key fix 4 R2 [1]", -2, params.getColumnPos(0));
+        assertEquals("5 key fix 4 R2 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key fix 4 R2 <3>", 0, params.getColumnPos(2));
+        assertEquals("5 key fix 4 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("5 key fix 4 R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 4 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 4 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [5] [6]
+    // [1] <2> [3] [4]
+    public void testLayout6KeyFix4M0() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_M0);
+        assertEquals("6 key fix 4 M0 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 M0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("6 key fix 4 M0 right", 3, params.mRightKeys);
+        assertEquals("6 key fix 4 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("6 key fix 4 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("6 key fix 4 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("6 key fix 4 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("6 key fix 4 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 4 M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 4 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5] [6]
+    // |<1> [2] [3] [4]
+    public void testLayout6KeyFix4L0() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_L0);
+        assertEquals("6 key fix 4 L0 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 L0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("6 key fix 4 L0 right", 4, params.mRightKeys);
+        assertEquals("6 key fix 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key fix 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key fix 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key fix 4 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("6 key fix 4 L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 4 L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6]
+    // |___ <1> [2] [3] [4]
+    public void testLayout6KeyFix4L1() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_L1);
+        assertEquals("6 key fix 4 L1 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 L1 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("6 key fix 4 L1 right", 4, params.mRightKeys);
+        assertEquals("6 key fix 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key fix 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key fix 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key fix 4 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("6 key fix 4 L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 4 L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [5] [6]
+    // |___ [1] <2> [3] [4]
+    public void testLayout6KeyFix4L2() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_L2);
+        assertEquals("6 key fix 4 L2 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 L2 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("6 key fix 4 L2 right", 3, params.mRightKeys);
+        assertEquals("6 key fix 4 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("6 key fix 4 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("6 key fix 4 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("6 key fix 4 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("6 key fix 4 L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 4 L2 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 4 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //         [5] [6]|
+    // [1] [2] [3] <4>|
+    public void testLayout6KeyFix4R0() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_R0);
+        assertEquals("6 key fix 4 R0 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 R0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 R0 left", 3, params.mLeftKeys);
+        assertEquals("6 key fix 4 R0 right", 1, params.mRightKeys);
+        assertEquals("6 key fix 4 R0 [1]", -3, params.getColumnPos(0));
+        assertEquals("6 key fix 4 R0 [2]", -2, params.getColumnPos(1));
+        assertEquals("6 key fix 4 R0 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key fix 4 R0 <4>", 0, params.getColumnPos(3));
+        assertEquals("6 key fix 4 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key fix 4 R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //         [5] [6] ___|
+    // [1] [2] [3] <4> ___|
+    public void testLayout6KeyFix4R1() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_R1);
+        assertEquals("6 key fix 4 R1 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 R1 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 R1 left", 3, params.mLeftKeys);
+        assertEquals("6 key fix 4 R1 right", 1, params.mRightKeys);
+        assertEquals("6 key fix 4 R1 [1]", -3, params.getColumnPos(0));
+        assertEquals("6 key fix 4 R1 [2]", -2, params.getColumnPos(1));
+        assertEquals("6 key fix 4 R1 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key fix 4 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key fix 4 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key fix 4 R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //       [5] [6]   ___|
+    // [1] [2] <3> [4] ___|
+    public void testLayout6KeyFix4R2() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_R2);
+        assertEquals("6 key fix 4 R2 columns", 4, params.mNumColumns);
+        assertEquals("6 key fix 4 R2 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 4 R2 left", 2, params.mLeftKeys);
+        assertEquals("6 key fix 4 R2 right", 2, params.mRightKeys);
+        assertEquals("6 key fix 4 R2 [1]", -2, params.getColumnPos(0));
+        assertEquals("6 key fix 4 R2 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key fix 4 R2 <3>", 0, params.getColumnPos(2));
+        assertEquals("6 key fix 4 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("6 key fix 4 R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 4 R2 [6]", 1, params.getColumnPos(5));
+        assertEquals("6 key fix 4 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("6 key fix 4 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [6] [7]
+    // [1] <2> [3] [4]
+    public void testLayout7KeyFix4M0() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_M0);
+        assertEquals("7 key fix 4 M0 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 M0 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("7 key fix 4 M0 right", 3, params.mRightKeys);
+        assertEquals("7 key fix 4 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("7 key fix 4 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("7 key fix 4 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("7 key fix 4 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key fix 4 M0 [5]", -1, params.getColumnPos(4));
+        assertEquals("7 key fix 4 M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 4 M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 4 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5] [6] [7]
+    // |<1> [2] [3] [4]
+    public void testLayout7KeyFix4L0() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_L0);
+        assertEquals("7 key fix 4 L0 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 L0 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key fix 4 L0 right", 4, params.mRightKeys);
+        assertEquals("7 key fix 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key fix 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key fix 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key fix 4 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key fix 4 L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key fix 4 L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key fix 4 L0 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key fix 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7]
+    // |___ <1> [2] [3] [4]
+    public void testLayout7KeyFix4L1() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_L1);
+        assertEquals("7 key fix 4 L1 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 L1 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key fix 4 L1 right", 4, params.mRightKeys);
+        assertEquals("7 key fix 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key fix 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key fix 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key fix 4 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key fix 4 L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key fix 4 L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key fix 4 l1 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key fix 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7]
+    // |___ [1] <2> [3] [4]
+    public void testLayout7KeyFix4L2() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_L2);
+        assertEquals("7 key fix 4 L2 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 L2 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key fix 4 L2 right", 3, params.mRightKeys);
+        assertEquals("7 key fix 4 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("7 key fix 4 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("7 key fix 4 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("7 key fix 4 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key fix 4 L2 [5]", -1, params.getColumnPos(4));
+        assertEquals("7 key fix 4 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 4 L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 4 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [6] [7]|
+    // [1] [2] [3] <4>|
+    public void testLayout7KeyFix4R0() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_R0);
+        assertEquals("7 key fix 4 R0 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 R0 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 R0 left", 3, params.mLeftKeys);
+        assertEquals("7 key fix 4 R0 right", 1, params.mRightKeys);
+        assertEquals("7 key fix 4 R0 [1]", -3, params.getColumnPos(0));
+        assertEquals("7 key fix 4 R0 [2]", -2, params.getColumnPos(1));
+        assertEquals("7 key fix 4 R0 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key fix 4 R0 <4>", 0, params.getColumnPos(3));
+        assertEquals("7 key fix 4 R0 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key fix 4 R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key fix 4 R0 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key fix 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [6] [7] ___|
+    // [1] [2] [3] <4> ___|
+    public void testLayout7KeyFix4R1() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_R1);
+        assertEquals("7 key fix 4 R1 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 R1 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 R1 left", 3, params.mLeftKeys);
+        assertEquals("7 key fix 4 R1 right", 1, params.mRightKeys);
+        assertEquals("7 key fix 4 R1 [1]", -3, params.getColumnPos(0));
+        assertEquals("7 key fix 4 R1 [2]", -2, params.getColumnPos(1));
+        assertEquals("7 key fix 4 R1 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key fix 4 R1 <4>", 0, params.getColumnPos(3));
+        assertEquals("7 key fix 4 R1 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key fix 4 R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key fix 4 R1 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key fix 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [6] [7] ___|
+    // [1] [2] <3> [4] ___|
+    public void testLayout7KeyFix4R2() {
+        MoreKeysKeyboardParams params = createParams(7, 4, XPOS_R2);
+        assertEquals("7 key fix 4 R2 columns", 4, params.mNumColumns);
+        assertEquals("7 key fix 4 R2 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 4 R2 left", 2, params.mLeftKeys);
+        assertEquals("7 key fix 4 R2 right", 2, params.mRightKeys);
+        assertEquals("7 key fix 4 R2 [1]", -2, params.getColumnPos(0));
+        assertEquals("7 key fix 4 R2 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key fix 4 R2 <3>", 0, params.getColumnPos(2));
+        assertEquals("7 key fix 4 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("7 key fix 4 R2 [5]", -1, params.getColumnPos(4));
+        assertEquals("7 key fix 4 R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 4 R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 4 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 4 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [6] [7] [8]
+    // [1] <2> [3] [4]
+    public void testLayout8KeyFix4M0() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_M0);
+        assertEquals("8 key fix 4 M0 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 M0 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("8 key fix 4 M0 right", 3, params.mRightKeys);
+        assertEquals("8 key fix 4 M0 [1]", -1, params.getColumnPos(0));
+        assertEquals("8 key fix 4 M0 <2>", 0, params.getColumnPos(1));
+        assertEquals("8 key fix 4 M0 [3]", 1, params.getColumnPos(2));
+        assertEquals("8 key fix 4 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key fix 4 M0 [5]", -1, params.getColumnPos(4));
+        assertEquals("8 key fix 4 M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("8 key fix 4 M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("8 key fix 4 M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key fix 4 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5] [6] [7] [8]
+    // |<1> [2] [3] [4]
+    public void testLayout8KeyFix4L0() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_L0);
+        assertEquals("8 key fix 4 L0 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 L0 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("8 key fix 4 L0 right", 4, params.mRightKeys);
+        assertEquals("8 key fix 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key fix 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key fix 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key fix 4 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key fix 4 L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key fix 4 L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key fix 4 L0 [7]", 2, params.getColumnPos(6));
+        assertEquals("8 key fix 4 L0 [8]", 3, params.getColumnPos(7));
+        assertEquals("8 key fix 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7] [8]
+    // |___ <1> [2] [3] [4]
+    public void testLayout8KeyFix4L1() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_L1);
+        assertEquals("8 key fix 4 L1 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 L1 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("8 key fix 4 L1 right", 4, params.mRightKeys);
+        assertEquals("8 key fix 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key fix 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key fix 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key fix 4 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key fix 4 L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key fix 4 L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key fix 4 L1 [7]", 2, params.getColumnPos(6));
+        assertEquals("8 key fix 4 L1 [8]", 3, params.getColumnPos(7));
+        assertEquals("8 key fix 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7] [8]
+    // |___ [1] <2> [3] [4]
+    public void testLayout8KeyFix4L2() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_L2);
+        assertEquals("8 key fix 4 L2 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 L2 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("8 key fix 4 L2 right", 3, params.mRightKeys);
+        assertEquals("8 key fix 4 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("8 key fix 4 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("8 key fix 4 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("8 key fix 4 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key fix 4 L2 [5]", -1, params.getColumnPos(4));
+        assertEquals("8 key fix 4 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("8 key fix 4 L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("8 key fix 4 L2 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key fix 4 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [6] [7] [8]|
+    // [1] [2] [3] <4>|
+    public void testLayout8KeyFix4R0() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_R0);
+        assertEquals("8 key fix 4 R0 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 R0 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 R0 left", 3, params.mLeftKeys);
+        assertEquals("8 key fix 4 R0 right", 1, params.mRightKeys);
+        assertEquals("8 key fix 4 R0 [1]", -3, params.getColumnPos(0));
+        assertEquals("8 key fix 4 R0 [2]", -2, params.getColumnPos(1));
+        assertEquals("8 key fix 4 R0 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key fix 4 R0 <4>", 0, params.getColumnPos(3));
+        assertEquals("8 key fix 4 R0 [5]", -3, params.getColumnPos(4));
+        assertEquals("8 key fix 4 R0 [6]", -2, params.getColumnPos(5));
+        assertEquals("8 key fix 4 R0 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key fix 4 R0 [8]", 0, params.getColumnPos(7));
+        assertEquals("8 key fix 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [6] [7] [8] ___|
+    // [1] [2] [3] <4> ___|
+    public void testLayout8KeyFix4R1() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_R1);
+        assertEquals("8 key fix 4 R1 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 R1 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 R1 left", 3, params.mLeftKeys);
+        assertEquals("8 key fix 4 R1 right", 1, params.mRightKeys);
+        assertEquals("8 key fix 4 R1 [1]", -3, params.getColumnPos(0));
+        assertEquals("8 key fix 4 R1 [2]", -2, params.getColumnPos(1));
+        assertEquals("8 key fix 4 R1 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key fix 4 R1 <4>", 0, params.getColumnPos(3));
+        assertEquals("8 key fix 4 R1 [5]", -3, params.getColumnPos(4));
+        assertEquals("8 key fix 4 R1 [6]", -2, params.getColumnPos(5));
+        assertEquals("8 key fix 4 R1 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key fix 4 R1 [8]", 0, params.getColumnPos(7));
+        assertEquals("8 key fix 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [6] [7] [8] ___|
+    // [1] [2] <3> [4] ___|
+    public void testLayout8KeyFix4R2() {
+        MoreKeysKeyboardParams params = createParams(8, 4, XPOS_R2);
+        assertEquals("8 key fix 4 R2 columns", 4, params.mNumColumns);
+        assertEquals("8 key fix 4 R2 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 4 R2 left", 2, params.mLeftKeys);
+        assertEquals("8 key fix 4 R2 right", 2, params.mRightKeys);
+        assertEquals("8 key fix 4 R2 [1]", -2, params.getColumnPos(0));
+        assertEquals("8 key fix 4 R2 [2]", -1, params.getColumnPos(1));
+        assertEquals("8 key fix 4 R2 <3>", 0, params.getColumnPos(2));
+        assertEquals("8 key fix 4 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("8 key fix 4 R2 [5]", -2, params.getColumnPos(4));
+        assertEquals("8 key fix 4 R2 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key fix 4 R2 [7]", 0, params.getColumnPos(6));
+        assertEquals("8 key fix 4 R2 [8]", 1, params.getColumnPos(7));
+        assertEquals("8 key fix 4 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 4 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+     // [1] [2] <3> [4] [5]
+    public void testLayout5KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_M0);
+        assertEquals("5 key fix 5 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 left", 2, params.mLeftKeys);
+        assertEquals("5 key fix 5 right", 3, params.mRightKeys);
+        assertEquals("5 key fix 5 [1]", -2, params.getColumnPos(0));
+        assertEquals("5 key fix 5 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key fix 5 <3>", 0, params.getColumnPos(2));
+        assertEquals("5 key fix 5 [4]", 1, params.getColumnPos(3));
+        assertEquals("5 key fix 5 [5]", 2, params.getColumnPos(4));
+        assertEquals("5 key fix 5 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4] [5]
+    public void testLayout5KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_L0);
+        assertEquals("5 key fix 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 L0 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key fix 5 L0 right", 5, params.mRightKeys);
+        assertEquals("5 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key fix 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("5 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout5KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_L1);
+        assertEquals("5 key fix 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 L1 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key fix 5 L1 right", 5, params.mRightKeys);
+        assertEquals("5 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key fix 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("5 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] <2> [3] [4] [5]
+    public void testLayout5KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_L2);
+        assertEquals("5 key fix 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 L2 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key fix 5 L2 right", 4, params.mRightKeys);
+        assertEquals("5 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("5 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("5 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("5 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("5 key fix 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("5 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] [4] <5>|
+    public void testLayout5KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_R0);
+        assertEquals("5 key fix 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 R0 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("5 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("5 key fix 5 R0 [1]", -4, params.getColumnPos(0));
+        assertEquals("5 key fix 5 R0 [2]", -3, params.getColumnPos(1));
+        assertEquals("5 key fix 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key fix 5 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("5 key fix 5 R0 <5>", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] [4] <5> ___|
+    public void testLayout5KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_R1);
+        assertEquals("5 key fix 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 R1 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("5 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("5 key fix 5 R1 [1]", -4, params.getColumnPos(0));
+        assertEquals("5 key fix 5 R1 [2]", -3, params.getColumnPos(1));
+        assertEquals("5 key fix 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key fix 5 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("5 key fix 5 R1 <5>", 0, params.getColumnPos(4));
+        assertEquals("5 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] <4> [5] ___|
+    public void testLayout5KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_R2);
+        assertEquals("5 key fix 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("5 key fix 5 R2 rows", 1, params.mNumRows);
+        assertEquals("5 key fix 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("5 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("5 key fix 5 R2 [1]", -3, params.getColumnPos(0));
+        assertEquals("5 key fix 5 R2 [2]", -2, params.getColumnPos(1));
+        assertEquals("5 key fix 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key fix 5 R2 <4>", 0, params.getColumnPos(3));
+        assertEquals("5 key fix 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key fix 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //         [6]
+    // [1] [2] <3> [4] [5]
+    public void testLayout6KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_M0);
+        assertEquals("6 key fix 5 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 left", 2, params.mLeftKeys);
+        assertEquals("6 key fix 5 right", 3, params.mRightKeys);
+        assertEquals("6 key fix 5 [1]", -2, params.getColumnPos(0));
+        assertEquals("6 key fix 5 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key fix 5 <3>", 0, params.getColumnPos(2));
+        assertEquals("6 key fix 5 [4]", 1, params.getColumnPos(3));
+        assertEquals("6 key fix 5 [5]", 2, params.getColumnPos(4));
+        assertEquals("6 key fix 5 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout6KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_L0);
+        assertEquals("6 key fix 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 L0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("6 key fix 5 L0 right", 5, params.mRightKeys);
+        assertEquals("6 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("6 key fix 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("6 key fix 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout6KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_L1);
+        assertEquals("6 key fix 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 L1 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("6 key fix 5 L1 right", 5, params.mRightKeys);
+        assertEquals("6 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("6 key fix 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("6 key fix 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___     [6]
+    // |___ [1] <2> [3] [4] [5]
+    public void testLayout6KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_L2);
+        assertEquals("6 key fix 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 L2 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("6 key fix 5 L2 right", 4, params.mRightKeys);
+        assertEquals("6 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("6 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("6 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("6 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("6 key fix 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("6 key fix 5 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //                 [6]|
+    // [1] [2] [3] [4] <5>|
+    public void testLayout6KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_R0);
+        assertEquals("6 key fix 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 R0 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("6 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("6 key fix 5 R0 [1]", -4, params.getColumnPos(0));
+        assertEquals("6 key fix 5 R0 [2]", -3, params.getColumnPos(1));
+        assertEquals("6 key fix 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key fix 5 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("6 key fix 5 R0 <5>", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 5 R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //                 [6] ___|
+    // [1] [2] [3] [4] <5> ___|
+    public void testLayout6KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_R1);
+        assertEquals("6 key fix 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 R1 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("6 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("6 key fix 5 R1 [1]", -4, params.getColumnPos(0));
+        assertEquals("6 key fix 5 R1 [2]", -3, params.getColumnPos(1));
+        assertEquals("6 key fix 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key fix 5 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("6 key fix 5 R1 <5>", 0, params.getColumnPos(4));
+        assertEquals("6 key fix 5 R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //             [6]     ___|
+    // [1] [2] [3] <4> [5] ___|
+    public void testLayout6KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_R2);
+        assertEquals("6 key fix 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("6 key fix 5 R2 rows", 2, params.mNumRows);
+        assertEquals("6 key fix 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("6 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("6 key fix 5 R2 [1]", -3, params.getColumnPos(0));
+        assertEquals("6 key fix 5 R2 [2]", -2, params.getColumnPos(1));
+        assertEquals("6 key fix 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key fix 5 R2 <4>", 0, params.getColumnPos(3));
+        assertEquals("6 key fix 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key fix 5 R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("6 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key fix 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //       [6] [7]
+    // [1] [2] <3> [4] [5]
+    public void testLayout7KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_M0);
+        assertEquals("7 key fix 5 columns", 5, params.mNumColumns);
+        assertEquals("7 key fix 5 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 left", 2, params.mLeftKeys);
+        assertEquals("7 key fix 5 right", 3, params.mRightKeys);
+        assertEquals("7 key fix 5 [1]", -2, params.getColumnPos(0));
+        assertEquals("7 key fix 5 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key fix 5 <3>", 0, params.getColumnPos(2));
+        assertEquals("7 key fix 5 [4]", 1, params.getColumnPos(3));
+        assertEquals("7 key fix 5 [5]", 2, params.getColumnPos(4));
+        assertEquals("7 key fix 5 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 5 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 5 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout7KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_L0);
+        assertEquals("7 key fix 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("7 key fix 5 L0 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key fix 5 L0 right", 5, params.mRightKeys);
+        assertEquals("7 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key fix 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("7 key fix 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 5 L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout7KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_L1);
+        assertEquals("7 key fix 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("7 key fix 5 L1 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key fix 5 L1 right", 5, params.mRightKeys);
+        assertEquals("7 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key fix 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("7 key fix 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 5 L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [6] [7]
+    // |___ [1] <2> [3] [4] [5]
+    public void testLayout7KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_L2);
+        assertEquals("7 key fix 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("7 key fix 5 L2 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key fix 5 L2 right", 4, params.mRightKeys);
+        assertEquals("7 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("7 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("7 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("7 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key fix 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("7 key fix 5 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 5 L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 5 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //             [6] [7]|
+    // [1] [2] [3] [4] <5>|
+    public void testLayout7KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_R0);
+        assertEquals("7 key fix 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("7 key fix 5 R0 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("7 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("7 key fix 5 R0 [1]", -4, params.getColumnPos(0));
+        assertEquals("7 key fix 5 R0 [2]", -3, params.getColumnPos(1));
+        assertEquals("7 key fix 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key fix 5 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("7 key fix 5 R0 <5>", 0, params.getColumnPos(4));
+        assertEquals("7 key fix 5 R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key fix 5 R0 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //             [6] [7] ___|
+    // [1] [2] [3] [4] <5> ___|
+    public void testLayout7KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_R1);
+        assertEquals("7 key fix 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("7 key fix 5 R1 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("7 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("7 key fix 5 R1 [1]", -4, params.getColumnPos(0));
+        assertEquals("7 key fix 5 R1 [2]", -3, params.getColumnPos(1));
+        assertEquals("7 key fix 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key fix 5 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("7 key fix 5 R1 <5>", 0, params.getColumnPos(4));
+        assertEquals("7 key fix 5 R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key fix 5 R1 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //           [6] [7]   ___|
+    // [1] [2] [3] <4> [5] ___|
+    public void testLayout7KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_R2);
+        assertEquals("7 key fix 5 R2 columns",5, params.mNumColumns);
+        assertEquals("7 key fix 5 R2 rows", 2, params.mNumRows);
+        assertEquals("7 key fix 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("7 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("7 key fix 5 R2 [1]", -3, params.getColumnPos(0));
+        assertEquals("7 key fix 5 R2 [2]", -2, params.getColumnPos(1));
+        assertEquals("7 key fix 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key fix 5 R2 <4>", 0, params.getColumnPos(3));
+        assertEquals("7 key fix 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key fix 5 R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 5 R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 5 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key fix 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //     [6] [7] [8]
+    // [1] [2] <3> [4] [5]
+    public void testLayout8KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_M0);
+        assertEquals("8 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 M0 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("8 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("8 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("8 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("8 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("8 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("8 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("8 key fix 5 M0 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key fix 5 M0 [7]", 0, params.getColumnPos(6));
+        assertEquals("8 key fix 5 M0 [8]", 1, params.getColumnPos(7));
+        assertEquals("8 key fix 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7] [8]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout8KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_L0);
+        assertEquals("8 key fix 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 L0 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("8 key fix 5 L0 right", 5, params.mRightKeys);
+        assertEquals("8 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key fix 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("8 key fix 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("8 key fix 5 L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("8 key fix 5 L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout8KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_L1);
+        assertEquals("8 key fix 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 L1 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("8 key fix 5 L1 right", 5, params.mRightKeys);
+        assertEquals("8 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key fix 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("8 key fix 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("8 key fix 5 L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("8 key fix 5 L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8]
+    // |___ [1] <2> [3] [4] [5]
+    public void testLayout8KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_L2);
+        assertEquals("8 key fix 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 L2 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("8 key fix 5 L2 right", 4, params.mRightKeys);
+        assertEquals("8 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("8 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("8 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("8 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key fix 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("8 key fix 5 L2 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key fix 5 L2 [7]", 0, params.getColumnPos(6));
+        assertEquals("8 key fix 5 L2 [8]", 1, params.getColumnPos(7));
+        assertEquals("8 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //         [6] [7] [8]|
+    // [1] [2] [3] [4] <5>|
+    public void testLayout8KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_R0);
+        assertEquals("8 key fix 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 R0 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("8 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("8 key fix 5 R0 [1]", -4, params.getColumnPos(0));
+        assertEquals("8 key fix 5 R0 [2]", -3, params.getColumnPos(1));
+        assertEquals("8 key fix 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("8 key fix 5 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("8 key fix 5 R0 <5>", 0, params.getColumnPos(4));
+        assertEquals("8 key fix 5 R0 [6]", -2, params.getColumnPos(5));
+        assertEquals("8 key fix 5 R0 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key fix 5 R0 [8]", 0, params.getColumnPos(7));
+        assertEquals("8 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //         [6] [7] [8] ___|
+    // [1] [2] [3] [4] <5> ___|
+    public void testLayout8KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_R1);
+        assertEquals("8 key fix 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 R1 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("8 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("8 key fix 5 R1 [1]", -4, params.getColumnPos(0));
+        assertEquals("8 key fix 5 R1 [2]", -3, params.getColumnPos(1));
+        assertEquals("8 key fix 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("8 key fix 5 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("8 key fix 5 R1 <5>", 0, params.getColumnPos(4));
+        assertEquals("8 key fix 5 R1 [6]", -2, params.getColumnPos(5));
+        assertEquals("8 key fix 5 R1 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key fix 5 R1 [8]", 0, params.getColumnPos(7));
+        assertEquals("8 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //         [6] [7] [8] ___|
+    // [1] [2] [3] <4> [5] ___|
+    public void testLayout8KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_R2);
+        assertEquals("8 key fix 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("8 key fix 5 R2 rows", 2, params.mNumRows);
+        assertEquals("8 key fix 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("8 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("8 key fix 5 R2 [1]", -3, params.getColumnPos(0));
+        assertEquals("8 key fix 5 R2 [2]", -2, params.getColumnPos(1));
+        assertEquals("8 key fix 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key fix 5 R2 <4>", 0, params.getColumnPos(3));
+        assertEquals("8 key fix 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("8 key fix 5 R2 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key fix 5 R2 [7]", 0, params.getColumnPos(6));
+        assertEquals("8 key fix 5 R2 [8]", 1, params.getColumnPos(7));
+        assertEquals("8 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key fix 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //   [6] [7] [8] [9]
+    // [1] [2] <3> [4] [5]
+    public void testLayout9KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_M0);
+        assertEquals("9 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 M0 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("9 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("9 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("9 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("9 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("9 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("9 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("9 key fix 5 M0 [6]", -1, params.getColumnPos(5));
+        assertEquals("9 key fix 5 M0 [7]", 0, params.getColumnPos(6));
+        assertEquals("9 key fix 5 M0 [8]", 1, params.getColumnPos(7));
+        assertEquals("9 key fix 5 M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("9 key fix 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7] [8] [9]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout9KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_L0);
+        assertEquals("9 key fix 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 L0 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("9 key fix 5 L0 right", 5, params.mRightKeys);
+        assertEquals("9 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("9 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("9 key fix 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("9 key fix 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key fix 5 L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key fix 5 L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key fix 5 L0 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8] [9]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout9KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_L1);
+        assertEquals("9 key fix 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 L1 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("9 key fix 5 L1 right", 5, params.mRightKeys);
+        assertEquals("9 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("9 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("9 key fix 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("9 key fix 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key fix 5 L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key fix 5 L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key fix 5 L1 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key fix 5 L1 adjust",0, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [6] [7] [8] [9]
+    // |___ [1] <2> [3] [4] [5]
+    public void testLayout9KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_L2);
+        assertEquals("9 key fix 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 L2 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("9 key fix 5 L2 right", 4, params.mRightKeys);
+        assertEquals("9 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("9 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("9 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("9 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("9 key fix 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("9 key fix 5 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key fix 5 L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key fix 5 L2 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key fix 5 L2 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key fix 5 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [6] [7] [8] [9]|
+    // [1] [2] [3] [4] <5>|
+    public void testLayout9KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_R0);
+        assertEquals("9 key fix 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 R0 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("9 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("9 key fix 5 R0 [1]", -4, params.getColumnPos(0));
+        assertEquals("9 key fix 5 R0 [2]", -3, params.getColumnPos(1));
+        assertEquals("9 key fix 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("9 key fix 5 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("9 key fix 5 R0 <5>", 0, params.getColumnPos(4));
+        assertEquals("9 key fix 5 R0 [6]", -3, params.getColumnPos(5));
+        assertEquals("9 key fix 5 R0 [7]", -2, params.getColumnPos(6));
+        assertEquals("9 key fix 5 R0 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key fix 5 R0 [9]", 0, params.getColumnPos(8));
+        assertEquals("9 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //     [6] [7] [8] [9] ___|
+    // [1] [2] [3] [4] <5> ___|
+    public void testLayout9KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_R1);
+        assertEquals("9 key fix 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 R1 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("9 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("9 key fix 5 R1 [1]", -4, params.getColumnPos(0));
+        assertEquals("9 key fix 5 R1 [2]", -3, params.getColumnPos(1));
+        assertEquals("9 key fix 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("9 key fix 5 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("9 key fix 5 R1 <5>", 0, params.getColumnPos(4));
+        assertEquals("9 key fix 5 R1 [6]", -3, params.getColumnPos(5));
+        assertEquals("9 key fix 5 R1 [7]", -2, params.getColumnPos(6));
+        assertEquals("9 key fix 5 R1 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key fix 5 R1 [9]", 0, params.getColumnPos(8));
+        assertEquals("9 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //   [6] [7] [8] [9]  ___|
+    // [1] [2] [3] <4> [5] ___|
+    public void testLayout9KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_R2);
+        assertEquals("9 key fix 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("9 key fix 5 R2 rows", 2, params.mNumRows);
+        assertEquals("9 key fix 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("9 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("9 key fix 5 R2 [1]", -3, params.getColumnPos(0));
+        assertEquals("9 key fix 5 R2 [2]", -2, params.getColumnPos(1));
+        assertEquals("9 key fix 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key fix 5 R2 <4>", 0, params.getColumnPos(3));
+        assertEquals("9 key fix 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("9 key fix 5 R2 [6]", -2, params.getColumnPos(5));
+        assertEquals("9 key fix 5 R2 [7]", -1, params.getColumnPos(6));
+        assertEquals("9 key fix 5 R2 [8]", 0, params.getColumnPos(7));
+        assertEquals("9 key fix 5 R2 [9]", 1, params.getColumnPos(8));
+        assertEquals("9 key fix 5 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key fix 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [7] [8] [9] [A]
+    // [1] [2] <3> [4] [5]
+    public void testLayout10KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_M0);
+        assertEquals("10 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 M0 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("10 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("10 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("10 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("10 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("10 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("10 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("10 key fix 5 M0 [6]", -2, params.getColumnPos(5));
+        assertEquals("10 key fix 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("10 key fix 5 M0 [8]", 0, params.getColumnPos(7));
+        assertEquals("10 key fix 5 M0 [9]", 1, params.getColumnPos(8));
+        assertEquals("10 key fix 5 M0 [A]", 2, params.getColumnPos(9));
+        assertEquals("10 key fix 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7] [8] [9] [A]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout10KeyFix5L0() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_L0);
+        assertEquals("10 key fix 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 L0 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("10 key fix 5 L0 right", 5, params.mRightKeys);
+        assertEquals("10 key fix 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key fix 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key fix 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("10 key fix 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("10 key fix 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("10 key fix 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key fix 5 L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key fix 5 L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("10 key fix 5 L0 [9]", 3, params.getColumnPos(8));
+        assertEquals("10 key fix 5 L0 [A]", 4, params.getColumnPos(9));
+        assertEquals("10 key fix 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8] [9] [A]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout10KeyFix5L1() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_L1);
+        assertEquals("10 key fix 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 L1 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("10 key fix 5 L1 right", 5, params.mRightKeys);
+        assertEquals("10 key fix 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key fix 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key fix 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("10 key fix 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("10 key fix 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("10 key fix 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key fix 5 L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key fix 5 L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("10 key fix 5 L1 [9]", 3, params.getColumnPos(8));
+        assertEquals("10 key fix 5 L1 [A]", 4, params.getColumnPos(9));
+        assertEquals("10 key fix 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8] [9] [A]
+    // |___ [1] <2> [3] [4] [5]
+    public void testLayout10KeyFix5L2() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_L2);
+        assertEquals("10 key fix 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 L2 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("10 key fix 5 L2 right", 4, params.mRightKeys);
+        assertEquals("10 key fix 5 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("10 key fix 5 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("10 key fix 5 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("10 key fix 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("10 key fix 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("10 key fix 5 L2 [6]", -1, params.getColumnPos(5));
+        assertEquals("10 key fix 5 L2 [7]", 0, params.getColumnPos(6));
+        assertEquals("10 key fix 5 L2 [8]", 1, params.getColumnPos(7));
+        assertEquals("10 key fix 5 L2 [9]", 2, params.getColumnPos(8));
+        assertEquals("10 key fix 5 L2 [A]", 3, params.getColumnPos(9));
+        assertEquals("10 key fix 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [7] [8] [9] [A]|
+    // [1] [2] [3] [4] <5>|
+    public void testLayout10KeyFix5R0() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_R0);
+        assertEquals("10 key fix 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 R0 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("10 key fix 5 R0 right", 1, params.mRightKeys);
+        assertEquals("10 key fix 5 R0 [1]", -4, params.getColumnPos(0));
+        assertEquals("10 key fix 5 R0 [2]", -3, params.getColumnPos(1));
+        assertEquals("10 key fix 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("10 key fix 5 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("10 key fix 5 R0 <5>", 0, params.getColumnPos(4));
+        assertEquals("10 key fix 5 R0 [6]", -4, params.getColumnPos(5));
+        assertEquals("10 key fix 5 R0 [7]", -3, params.getColumnPos(6));
+        assertEquals("10 key fix 5 R0 [8]", -2, params.getColumnPos(7));
+        assertEquals("10 key fix 5 R0 [9]", -1, params.getColumnPos(8));
+        assertEquals("10 key fix 5 R0 [A]", 0, params.getColumnPos(9));
+        assertEquals("10 key fix 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [7] [8] [9] [A] ___|
+    // [1] [2] [3] [4] <5> ___|
+    public void testLayout10KeyFix5R1() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_R1);
+        assertEquals("10 key fix 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 R1 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("10 key fix 5 R1 right", 1, params.mRightKeys);
+        assertEquals("10 key fix 5 R1 [1]", -4, params.getColumnPos(0));
+        assertEquals("10 key fix 5 R1 [2]", -3, params.getColumnPos(1));
+        assertEquals("10 key fix 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("10 key fix 5 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("10 key fix 5 R1 <5>", 0, params.getColumnPos(4));
+        assertEquals("10 key fix 5 R1 [6]", -4, params.getColumnPos(5));
+        assertEquals("10 key fix 5 R1 [7]", -3, params.getColumnPos(6));
+        assertEquals("10 key fix 5 R1 [8]", -2, params.getColumnPos(7));
+        assertEquals("10 key fix 5 R1 [9]", -1, params.getColumnPos(8));
+        assertEquals("10 key fix 5 R1 [A]", 0, params.getColumnPos(9));
+        assertEquals("10 key fix 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [7] [8] [9] [A] ___|
+    // [1] [2] [3] <4> [5] ___|
+    public void testLayout10KeyFix5R2() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_R2);
+        assertEquals("10 key fix 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("10 key fix 5 R2 rows", 2, params.mNumRows);
+        assertEquals("10 key fix 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("10 key fix 5 R2 right", 2, params.mRightKeys);
+        assertEquals("10 key fix 5 R2 [1]", -3, params.getColumnPos(0));
+        assertEquals("10 key fix 5 R2 [2]", -2, params.getColumnPos(1));
+        assertEquals("10 key fix 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key fix 5 R2 <4>", 0, params.getColumnPos(3));
+        assertEquals("10 key fix 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("10 key fix 5 R2 [6]", -3, params.getColumnPos(5));
+        assertEquals("10 key fix 5 R2 [7]", -2, params.getColumnPos(6));
+        assertEquals("10 key fix 5 R2 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key fix 5 R2 [9]", 0, params.getColumnPos(8));
+        assertEquals("10 key fix 5 R2 [A]", 1, params.getColumnPos(9));
+        assertEquals("10 key fix 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key fix 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //         [B]
+    // [6] [7] [8] [9] [A]
+    // [1] [2] <3> [4] [5]
+    public void testLayout11KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(11, 5, XPOS_M0);
+        assertEquals("11 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("11 key fix 5 M0 rows", 3, params.mNumRows);
+        assertEquals("11 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("11 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("11 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("11 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("11 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("11 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("11 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("11 key fix 5 M0 [6]", -2, params.getColumnPos(5));
+        assertEquals("11 key fix 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("11 key fix 5 M0 [8]", 0, params.getColumnPos(7));
+        assertEquals("11 key fix 5 M0 [9]", 1, params.getColumnPos(8));
+        assertEquals("11 key fix 5 M0 [A]", 2, params.getColumnPos(9));
+        assertEquals("11 key fix 5 M0 [B]", 0, params.getColumnPos(10));
+        assertEquals("11 key fix 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("11 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //       [B] [C]
+    // [6] [7] [8] [9] [A]
+    // [1] [2] <3> [4] [5]
+    public void testLayout12KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(12, 5, XPOS_M0);
+        assertEquals("12 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("12 key fix 5 M0 rows", 3, params.mNumRows);
+        assertEquals("12 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("12 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("12 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("12 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("12 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("12 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("12 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("12 key fix 5 M0 [6]", -2, params.getColumnPos(5));
+        assertEquals("12 key fix 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("12 key fix 5 M0 [8]", 0, params.getColumnPos(7));
+        assertEquals("12 key fix 5 M0 [9]", 1, params.getColumnPos(8));
+        assertEquals("12 key fix 5 M0 [A]", 2, params.getColumnPos(9));
+        assertEquals("12 key fix 5 M0 [B]", 0, params.getColumnPos(10));
+        assertEquals("12 key fix 5 M0 [C]", 1, params.getColumnPos(11));
+        assertEquals("12 key fix 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("12 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [B] [C] [D]
+    // [6] [7] [8] [9] [A]
+    // [1] [2] <3> [4] [5]
+    public void testLayout13KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(13, 5, XPOS_M0);
+        assertEquals("13 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("13 key fix 5 M0 rows", 3, params.mNumRows);
+        assertEquals("13 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("13 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("13 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("13 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("13 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("13 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("13 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("13 key fix 5 M0 [6]", -2, params.getColumnPos(5));
+        assertEquals("13 key fix 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("13 key fix 5 M0 [8]", 0, params.getColumnPos(7));
+        assertEquals("13 key fix 5 M0 [9]", 1, params.getColumnPos(8));
+        assertEquals("13 key fix 5 M0 [A]", 2, params.getColumnPos(9));
+        assertEquals("13 key fix 5 M0 [B]", -1, params.getColumnPos(10));
+        assertEquals("13 key fix 5 M0 [C]", 0, params.getColumnPos(11));
+        assertEquals("13 key fix 5 M0 [D]", 1, params.getColumnPos(12));
+        assertEquals("13 key fix 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("13 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [B] [C] [D] [E]
+    // [6] [7] [8] [9] [A]
+    // [1] [2] <3> [4] [5]
+    public void testLayout14KeyFix5M0() {
+        MoreKeysKeyboardParams params = createParams(14, 5, XPOS_M0);
+        assertEquals("14 key fix 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("14 key fix 5 M0 rows", 3, params.mNumRows);
+        assertEquals("14 key fix 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("14 key fix 5 M0 right", 3, params.mRightKeys);
+        assertEquals("14 key fix 5 M0 [1]", -2, params.getColumnPos(0));
+        assertEquals("14 key fix 5 M0 [2]", -1, params.getColumnPos(1));
+        assertEquals("14 key fix 5 M0 <3>", 0, params.getColumnPos(2));
+        assertEquals("14 key fix 5 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("14 key fix 5 M0 [5]", 2, params.getColumnPos(4));
+        assertEquals("14 key fix 5 M0 [6]", -2, params.getColumnPos(5));
+        assertEquals("14 key fix 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("14 key fix 5 M0 [8]", 0, params.getColumnPos(7));
+        assertEquals("14 key fix 5 M0 [9]", 1, params.getColumnPos(8));
+        assertEquals("14 key fix 5 M0 [A]", 2, params.getColumnPos(9));
+        assertEquals("14 key fix 5 M0 [B]", -1, params.getColumnPos(10));
+        assertEquals("14 key fix 5 M0 [C]", 0, params.getColumnPos(11));
+        assertEquals("14 key fix 5 M0 [D]", 1, params.getColumnPos(12));
+        assertEquals("14 key fix 5 M0 [E]", 2, params.getColumnPos(13));
+        assertEquals("14 key fix 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("14 key fix 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4] [5] [6] [7]
+    public void testLayout7KeyFix7L0() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L0);
+        assertEquals("7 key fix 7 L0 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 L0 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key fix 7 L0 right", 7, params.mRightKeys);
+        assertEquals("7 key fix 7 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key fix 7 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key fix 7 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key fix 7 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key fix 7 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("7 key fix 7 L0 [6]", 5, params.getColumnPos(5));
+        assertEquals("7 key fix 7 L0 [7]", 6, params.getColumnPos(6));
+        assertEquals("7 key fix 7 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4] [5] [6] [7]
+    public void testLayout7KeyFix7L1() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L1);
+        assertEquals("7 key fix 7 L1 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 L1 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key fix 7 L1 right", 7, params.mRightKeys);
+        assertEquals("7 key fix 7 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key fix 7 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key fix 7 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key fix 7 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key fix 7 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("7 key fix 7 L1 [6]", 5, params.getColumnPos(5));
+        assertEquals("7 key fix 7 L1 [7]", 6, params.getColumnPos(6));
+        assertEquals("7 key fix 7 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] <2> [3] [4] [5] [6] [7]
+    public void testLayout7KeyFix7L2() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L2);
+        assertEquals("7 key fix 7 L2 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 L2 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key fix 7 L2 right", 6, params.mRightKeys);
+        assertEquals("7 key fix 7 L2 [1]", -1, params.getColumnPos(0));
+        assertEquals("7 key fix 7 L2 <2>", 0, params.getColumnPos(1));
+        assertEquals("7 key fix 7 L2 [3]", 1, params.getColumnPos(2));
+        assertEquals("7 key fix 7 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key fix 7 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("7 key fix 7 L2 [6]", 4, params.getColumnPos(5));
+        assertEquals("7 key fix 7 L2 [7]", 5, params.getColumnPos(6));
+        assertEquals("7 key fix 7 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] [2] <3> [4] [5] [6] [7]
+    public void testLayout7KeyFix7L3() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L3);
+        assertEquals("7 key fix 7 L3 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 L3 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 L3 left", 2, params.mLeftKeys);
+        assertEquals("7 key fix 7 L3 right", 5, params.mRightKeys);
+        assertEquals("7 key fix 7 L3 [1]", -2, params.getColumnPos(0));
+        assertEquals("7 key fix 7 L3 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key fix 7 L3 <3>", 0, params.getColumnPos(2));
+        assertEquals("7 key fix 7 L3 [4]", 1, params.getColumnPos(3));
+        assertEquals("7 key fix 7 L3 [5]", 2, params.getColumnPos(4));
+        assertEquals("7 key fix 7 L3 [6]", 3, params.getColumnPos(5));
+        assertEquals("7 key fix 7 L3 [7]", 4, params.getColumnPos(6));
+        assertEquals("7 key fix 7 L3 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 L3 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] [2] [3] <4> [5] [6] [7] ___ ___|
+    public void testLayout7KeyFix7M0() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_M0);
+        assertEquals("7 key fix 7 M0 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 M0 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 M0 left", 3, params.mLeftKeys);
+        assertEquals("7 key fix 7 M0 right", 4, params.mRightKeys);
+        assertEquals("7 key fix 7 M0 [1]", -3, params.getColumnPos(0));
+        assertEquals("7 key fix 7 M0 [2]", -2, params.getColumnPos(1));
+        assertEquals("7 key fix 7 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key fix 7 M0 <4>", 0, params.getColumnPos(3));
+        assertEquals("7 key fix 7 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key fix 7 M0 [6]", 2, params.getColumnPos(5));
+        assertEquals("7 key fix 7 M0 [7]", 3, params.getColumnPos(6));
+        assertEquals("7 key fix 7 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 M0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [1] [2] [3] <4> [5] [6] [7] ___|
+    public void testLayout7KeyFix7M1() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_M1);
+        assertEquals("7 key fix 7 M1 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 M1 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 M1 left", 3, params.mLeftKeys);
+        assertEquals("7 key fix 7 M1 right", 4, params.mRightKeys);
+        assertEquals("7 key fix 7 M1 [1]", -3, params.getColumnPos(0));
+        assertEquals("7 key fix 7 M1 [2]", -2, params.getColumnPos(1));
+        assertEquals("7 key fix 7 M1 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key fix 7 M1 <4>", 0, params.getColumnPos(3));
+        assertEquals("7 key fix 7 M1 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key fix 7 M1 [6]", 2, params.getColumnPos(5));
+        assertEquals("7 key fix 7 M1 [7]", 3, params.getColumnPos(6));
+        assertEquals("7 key fix 7 M1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 M1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] [4] <5> [6] [7] ___|
+    public void testLayout7KeyFix7R3() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R3);
+        assertEquals("7 key fix 7 R3 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 R3 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 R3 left", 4, params.mLeftKeys);
+        assertEquals("7 key fix 7 R3 right", 3, params.mRightKeys);
+        assertEquals("7 key fix 7 R3 [1]", -4, params.getColumnPos(0));
+        assertEquals("7 key fix 7 R3 [2]", -3, params.getColumnPos(1));
+        assertEquals("7 key fix 7 R3 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key fix 7 R3 [4]", -1, params.getColumnPos(3));
+        assertEquals("7 key fix 7 R3 <5>", 0, params.getColumnPos(4));
+        assertEquals("7 key fix 7 R3 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key fix 7 R3 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key fix 7 R3 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 R3 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] [4] [5] <6> [7] ___|
+    public void testLayout7KeyFix7R2() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R2);
+        assertEquals("7 key fix 7 R2 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 R2 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 R2 left", 5, params.mLeftKeys);
+        assertEquals("7 key fix 7 R2 right", 2, params.mRightKeys);
+        assertEquals("7 key fix 7 R2 [1]", -5, params.getColumnPos(0));
+        assertEquals("7 key fix 7 R2 [2]", -4, params.getColumnPos(1));
+        assertEquals("7 key fix 7 R2 [3]", -3, params.getColumnPos(2));
+        assertEquals("7 key fix 7 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("7 key fix 7 R2 [5]", -1, params.getColumnPos(4));
+        assertEquals("7 key fix 7 R2 <6>", 0, params.getColumnPos(5));
+        assertEquals("7 key fix 7 R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("7 key fix 7 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 R2 default", WIDTH * 5, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] [4] [5] [6] <7> ___|
+    public void testLayout7KeyFix7R1() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R1);
+        assertEquals("7 key fix 7 R1 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 R1 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 R1 left", 6, params.mLeftKeys);
+        assertEquals("7 key fix 7 R1 right", 1, params.mRightKeys);
+        assertEquals("7 key fix 7 R1 [1]", -6, params.getColumnPos(0));
+        assertEquals("7 key fix 7 R1 [2]", -5, params.getColumnPos(1));
+        assertEquals("7 key fix 7 R1 [3]", -4, params.getColumnPos(2));
+        assertEquals("7 key fix 7 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key fix 7 R1 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key fix 7 R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key fix 7 R1 <7>", 0, params.getColumnPos(6));
+        assertEquals("7 key fix 7 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 R1 default", WIDTH * 6, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] [3] [4] [5] [6] <7>|
+    public void testLayout7KeyFix7R0() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R0);
+        assertEquals("7 key fix 7 R0 columns", 7, params.mNumColumns);
+        assertEquals("7 key fix 7 R0 rows", 1, params.mNumRows);
+        assertEquals("7 key fix 7 R0 left", 6, params.mLeftKeys);
+        assertEquals("7 key fix 7 R0 right", 1, params.mRightKeys);
+        assertEquals("7 key fix 7 R0 [1]", -6, params.getColumnPos(0));
+        assertEquals("7 key fix 7 R0 [2]", -5, params.getColumnPos(1));
+        assertEquals("7 key fix 7 R0 [3]", -4, params.getColumnPos(2));
+        assertEquals("7 key fix 7 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key fix 7 R0 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key fix 7 R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key fix 7 R0 <7>", 0, params.getColumnPos(6));
+        assertEquals("7 key fix 7 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key fix 7 R0 default", WIDTH * 6, params.getDefaultKeyCoordX());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
new file mode 100644
index 0000000..31f0e0f
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -0,0 +1,2365 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams;
+
+public class MoreKeysKeyboardBuilderTests extends AndroidTestCase {
+    private static final int WIDTH = 10;
+    private static final int HEIGHT = 10;
+
+    private static final int KEYBOARD_WIDTH = WIDTH * 10;
+    private static final int XPOS_L0 = WIDTH * 0 + WIDTH / 2;
+    private static final int XPOS_L1 = WIDTH * 1 + WIDTH / 2;
+    private static final int XPOS_L2 = WIDTH * 2 + WIDTH / 2;
+    private static final int XPOS_L3 = WIDTH * 3 + WIDTH / 2;
+    private static final int XPOS_M0 = WIDTH * 4 + WIDTH / 2;
+    private static final int XPOS_M1 = WIDTH * 5 + WIDTH / 2;
+    private static final int XPOS_R3 = WIDTH * 6 + WIDTH / 2;
+    private static final int XPOS_R2 = WIDTH * 7 + WIDTH / 2;
+    private static final int XPOS_R1 = WIDTH * 8 + WIDTH / 2;
+    private static final int XPOS_R0 = WIDTH * 9 + WIDTH / 2;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    private static MoreKeysKeyboardParams createParams(int numKeys, int maxColumns,
+            int coordXInParnet) {
+        final MoreKeysKeyboardParams params = new MoreKeysKeyboardParams();
+        params.setParameters(numKeys, maxColumns, WIDTH, HEIGHT, coordXInParnet, KEYBOARD_WIDTH,
+                /* isFixedOrderColumn */false, /* dividerWidth */0);
+        return params;
+    }
+
+    public void testLayoutError() {
+        MoreKeysKeyboardParams params = null;
+        try {
+            final int maxColumns = KEYBOARD_WIDTH / WIDTH;
+            params = createParams(10, maxColumns + 1, HEIGHT);
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Too small keyboard to hold more keys keyboard.
+        }
+        assertNull("Too small keyboard to hold more keys keyboard", params);
+    }
+
+    // More keys keyboard layout test.
+    // "[n]" represents n-th key position in more keys keyboard.
+    // "<1>" is the default key.
+
+    // <1>
+    public void testLayout1KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_M0);
+        assertEquals("1 key max 5 M0 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 M0 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 M0 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 M0 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |<1>
+    public void testLayout1KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_L0);
+        assertEquals("1 key max 5 L0 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 L0 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 L0 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1>
+    public void testLayout1KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_L1);
+        assertEquals("1 key max 5 L1 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 L1 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 L1 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ <1>
+    public void testLayout1KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_L2);
+        assertEquals("1 key max 5 L2 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 L2 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 L2 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 L2 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1>|
+    public void testLayout1KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_R0);
+        assertEquals("1 key max 5 R0 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 R0 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 R0 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 R0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1> ___|
+    public void testLayout1KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_R1);
+        assertEquals("1 key max 5 R1 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 R1 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 R1 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 R1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1> ___ ___|
+    public void testLayout1KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(1, 5, XPOS_R2);
+        assertEquals("1 key max 5 R2 columns", 1, params.mNumColumns);
+        assertEquals("1 key max 5 R2 rows", 1, params.mNumRows);
+        assertEquals("1 key max 5 R2 left", 0, params.mLeftKeys);
+        assertEquals("1 key max 5 R2 right", 1, params.mRightKeys);
+        assertEquals("1 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("1 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key max 5 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // <1> [2]
+    public void testLayout2KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_M0);
+        assertEquals("2 key max 5 M0 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 M0 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 M0 left", 0, params.mLeftKeys);
+        assertEquals("2 key max 5 M0 right", 2, params.mRightKeys);
+        assertEquals("2 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2]
+    public void testLayout2KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_L0);
+        assertEquals("2 key max 5 L0 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 L0 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("2 key max 5 L0 right", 2, params.mRightKeys);
+        assertEquals("2 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2]
+    public void testLayout2KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_L1);
+        assertEquals("2 key max 5 L1 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 L1 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("2 key max 5 L1 right", 2, params.mRightKeys);
+        assertEquals("2 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ <1> [2]
+    public void testLayout2KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_L2);
+        assertEquals("2 key max 5 L2 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 L2 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 L2 left", 0, params.mLeftKeys);
+        assertEquals("2 key max 5 L2 right", 2, params.mRightKeys);
+        assertEquals("2 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [2] <1>|
+    public void testLayout2KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_R0);
+        assertEquals("2 key max 5 R0 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 R0 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 R0 left", 1, params.mLeftKeys);
+        assertEquals("2 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("2 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("2 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [2] <1> ___|
+    public void testLayout2KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_R1);
+        assertEquals("2 key max 5 R1 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 R1 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 R1 left", 1, params.mLeftKeys);
+        assertEquals("2 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("2 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("2 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // <1> [2] ___|
+    public void testLayout2KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(2, 5, XPOS_R2);
+        assertEquals("2 key max 5 R2 columns", 2, params.mNumColumns);
+        assertEquals("2 key max 5 R2 rows", 1, params.mNumRows);
+        assertEquals("2 key max 5 R2 left", 0, params.mLeftKeys);
+        assertEquals("2 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("2 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("2 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key max 5 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [3] <1> [2]
+    public void testLayout3KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_M0);
+        assertEquals("3 key max 5 M0 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 M0 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("3 key max 5 M0 right", 2, params.mRightKeys);
+        assertEquals("3 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3]
+    public void testLayout3KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_L0);
+        assertEquals("3 key max 5 L0 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 L0 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 5 L0 right", 3, params.mRightKeys);
+        assertEquals("3 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("3 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3]
+    public void testLayout3KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_L1);
+        assertEquals("3 key max 5 L1 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 L1 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 5 L1 right", 3, params.mRightKeys);
+        assertEquals("3 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("3 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] <1> [2]
+    public void testLayout3KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_L2);
+        assertEquals("3 key max 5 L2 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 L2 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("3 key max 5 L2 right", 2, params.mRightKeys);
+        assertEquals("3 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [2] <1>|
+    public void testLayout3KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_R0);
+        assertEquals("3 key max 5 R0 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 R0 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 R0 left", 2, params.mLeftKeys);
+        assertEquals("3 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("3 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("3 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [2] <1> ___|
+    public void testLayout3KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_R1);
+        assertEquals("3 key max 5 R1 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 R1 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 R1 left", 2, params.mLeftKeys);
+        assertEquals("3 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("3 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("3 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [3] <1> [2] ___|
+    public void testLayout3KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(3, 5, XPOS_R2);
+        assertEquals("3 key max 5 R2 columns", 3, params.mNumColumns);
+        assertEquals("3 key max 5 R2 rows", 1, params.mNumRows);
+        assertEquals("3 key max 5 R2 left", 1, params.mLeftKeys);
+        assertEquals("3 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("3 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 5 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3]
+    // <1> [2]
+    public void testLayout3KeyMax2M0() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_M0);
+        assertEquals("3 key max 2 M0 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 M0 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 M0 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 2 M0 right", 2, params.mRightKeys);
+        assertEquals("3 key max 2 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 2 M0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |[3]
+    // |<1> [2]
+    public void testLayout3KeyMax2L0() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_L0);
+        assertEquals("3 key max 2 L0 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 L0 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 L0 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 2 L0 right", 2, params.mRightKeys);
+        assertEquals("3 key max 2 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 2 L0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3]
+    // |___ <1> [2]
+    public void testLayout3KeyMax2L1() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_L1);
+        assertEquals("3 key max 2 L1 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 L1 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 L1 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 2 L1 right", 2, params.mRightKeys);
+        assertEquals("3 key max 2 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 2 L1 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |        [3]
+    // |___ ___ <1> [2]
+    public void testLayout3KeyMax2L2() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_L2);
+        assertEquals("3 key max 2 L2 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 L2 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 L2 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 2 L2 right", 2, params.mRightKeys);
+        assertEquals("3 key max 2 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 2 L2 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    //     [3]|
+    // [2] <1>|
+    public void testLayout3KeyMax2R0() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_R0);
+        assertEquals("3 key max 2 R0 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 R0 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 R0 left", 1, params.mLeftKeys);
+        assertEquals("3 key max 2 R0 right", 1, params.mRightKeys);
+        assertEquals("3 key max 2 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key max 2 R0 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [3]    |
+    // [2] <1> ___|
+    public void testLayout3KeyMax2R1() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_R1);
+        assertEquals("3 key max 2 R1 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 R1 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 R1 left", 1, params.mLeftKeys);
+        assertEquals("3 key max 2 R1 right", 1, params.mRightKeys);
+        assertEquals("3 key max 2 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key max 2 R1 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3]        |
+    // <1> [2] ___|
+    public void testLayout3KeyMax2R2() {
+        MoreKeysKeyboardParams params = createParams(3, 2, XPOS_R2);
+        assertEquals("3 key max 2 R2 columns", 2, params.mNumColumns);
+        assertEquals("3 key max 2 R2 rows", 2, params.mNumRows);
+        assertEquals("3 key max 2 R2 left", 0, params.mLeftKeys);
+        assertEquals("3 key max 2 R2 right", 2, params.mRightKeys);
+        assertEquals("3 key max 2 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("3 key max 2 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key max 2 R2 [3]", 0, params.getColumnPos(2));
+        assertEquals("3 key max 2 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key max 2 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [4]
+    // <1> [2]
+    public void testLayout4KeyMax3M0() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_M0);
+        assertEquals("4 key max 3 M0 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 M0 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 M0 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 3 M0 right", 2, params.mRightKeys);
+        assertEquals("4 key max 3 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 3 M0 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 M0 [4]", 1, params.getColumnPos(3));
+        assertEquals("4 key max 3 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |[3] [4]
+    // |<1> [2]
+    public void testLayout4KeyMax3L0() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_L0);
+        assertEquals("4 key max 3 L0 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 L0 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 L0 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 3 L0 right", 2, params.mRightKeys);
+        assertEquals("4 key max 3 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 3 L0 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 L0 [4]", 1, params.getColumnPos(3));
+        assertEquals("4 key max 3 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] [4]
+    // |___ <1> [2]
+    public void testLayout4KeyMax3L1() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_L1);
+        assertEquals("4 key max 3 L1 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 L1 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 L1 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 3 L1 right", 2, params.mRightKeys);
+        assertEquals("4 key max 3 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 3 L1 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 L1 [4]", 1, params.getColumnPos(3));
+        assertEquals("4 key max 3 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [3] [4]
+    // |___ ___ <1> [2]
+    public void testLayout4KeyMax3L2() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_L2);
+        assertEquals("4 key max 3 L2 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 L2 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 L2 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 3 L2 right", 2, params.mRightKeys);
+        assertEquals("4 key max 3 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 3 L2 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 L2 [4]", 1, params.getColumnPos(3));
+        assertEquals("4 key max 3 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3]|
+    // [2] <1>|
+    public void testLayout4KeyMax3R0() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_R0);
+        assertEquals("4 key max 3 R0 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 R0 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 R0 left", 1, params.mLeftKeys);
+        assertEquals("4 key max 3 R0 right", 1, params.mRightKeys);
+        assertEquals("4 key max 3 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key max 3 R0 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 R0 [4]", -1, params.getColumnPos(3));
+        assertEquals("4 key max 3 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] ___|
+    // [2] <1> ___|
+    public void testLayout4KeyMax3R1() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_R1);
+        assertEquals("4 key max 3 R1 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 R1 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 R1 left", 1, params.mLeftKeys);
+        assertEquals("4 key max 3 R1 right", 1, params.mRightKeys);
+        assertEquals("4 key max 3 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key max 3 R1 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 R1 [4]", -1, params.getColumnPos(3));
+        assertEquals("4 key max 3 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [4] ___|
+    // <1> [2] ___|
+    public void testLayout4KeyMax3R2() {
+        MoreKeysKeyboardParams params = createParams(4, 3, XPOS_R2);
+        assertEquals("4 key max 3 R2 columns", 2, params.mNumColumns);
+        assertEquals("4 key max 3 R2 rows", 2, params.mNumRows);
+        assertEquals("4 key max 3 R2 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 3 R2 right", 2, params.mRightKeys);
+        assertEquals("4 key max 3 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 3 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 3 R2 [3]", 0, params.getColumnPos(2));
+        assertEquals("4 key max 3 R2 [4]", 1, params.getColumnPos(3));
+        assertEquals("4 key max 3 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 3 R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [3] <1> [2] [4]
+    public void testLayout4KeyMax4M0() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_M0);
+        assertEquals("4 key max 4 M0 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 M0 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("4 key max 4 M0 right", 3, params.mRightKeys);
+        assertEquals("4 key max 4 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 4 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key max 4 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key max 4 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4]
+    public void testLayout4KeyMax4L0() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_L0);
+        assertEquals("4 key max 4 L0 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 L0 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 4 L0 right", 4, params.mRightKeys);
+        assertEquals("4 key max 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key max 4 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key max 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4]
+    public void testLayout4KeyMax4L1() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_L1);
+        assertEquals("4 key max 4 L1 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 L1 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 4 L1 right", 4, params.mRightKeys);
+        assertEquals("4 key max 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key max 4 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key max 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] <1> [2] [4]
+    public void testLayout4KeyMax4L2() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_L2);
+        assertEquals("4 key max 4 L2 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 L2 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("4 key max 4 L2 right", 3, params.mRightKeys);
+        assertEquals("4 key max 4 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 4 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key max 4 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key max 4 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [2] <1>|
+    public void testLayout4KeyMax4R0() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_R0);
+        assertEquals("4 key max 4 R0 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 R0 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 R0 left", 3, params.mLeftKeys);
+        assertEquals("4 key max 4 R0 right", 1, params.mRightKeys);
+        assertEquals("4 key max 4 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key max 4 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("4 key max 4 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("4 key max 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [2] <1> ___|
+    public void testLayout4KeyMax4R1() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_R1);
+        assertEquals("4 key max 4 R1 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 R1 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 R1 left", 3, params.mLeftKeys);
+        assertEquals("4 key max 4 R1 right", 1, params.mRightKeys);
+        assertEquals("4 key max 4 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key max 4 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("4 key max 4 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("4 key max 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] <1> [2] ___|
+    public void testLayout4KeyMax4R2() {
+        MoreKeysKeyboardParams params = createParams(4, 4, XPOS_R2);
+        assertEquals("4 key max 4 R2 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 4 R2 rows", 1, params.mNumRows);
+        assertEquals("4 key max 4 R2 left", 2, params.mLeftKeys);
+        assertEquals("4 key max 4 R2 right", 2, params.mRightKeys);
+        assertEquals("4 key max 4 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 4 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 4 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key max 4 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("4 key max 4 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 4 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [3] <1> [2] [4]
+    public void testLayout4KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_M0);
+        assertEquals("4 key max 5 M0 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 M0 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("4 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("4 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4]
+    public void testLayout4KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_L0);
+        assertEquals("4 key max 5 L0 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 L0 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 5 L0 right", 4, params.mRightKeys);
+        assertEquals("4 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key max 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4]
+    public void testLayout4KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_L1);
+        assertEquals("4 key max 5 L1 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 L1 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("4 key max 5 L1 right", 4, params.mRightKeys);
+        assertEquals("4 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key max 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] <1> [2] [4]
+    public void testLayout4KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_L2);
+        assertEquals("4 key max 5 L2 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 L2 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("4 key max 5 L2 right", 3, params.mRightKeys);
+        assertEquals("4 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key max 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [2] <1>|
+    public void testLayout4KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_R0);
+        assertEquals("4 key max 5 R0 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 R0 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 R0 left", 3, params.mLeftKeys);
+        assertEquals("4 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("4 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("4 key max 5 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("4 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [2] <1> ___|
+    public void testLayout4KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_R1);
+        assertEquals("4 key max 5 R1 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 R1 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 R1 left", 3, params.mLeftKeys);
+        assertEquals("4 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("4 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("4 key max 5 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("4 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] <1> [2] ___|
+    public void testLayout4KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(4, 5, XPOS_R2);
+        assertEquals("4 key max 5 R2 columns", 4, params.mNumColumns);
+        assertEquals("4 key max 5 R2 rows", 1, params.mNumRows);
+        assertEquals("4 key max 5 R2 left", 2, params.mLeftKeys);
+        assertEquals("4 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("4 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("4 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key max 5 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("4 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key max 5 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [4] [5]
+    // [3] <1> [2]
+    public void testLayout5KeyMax3M0() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_M0);
+        assertEquals("5 key max 3 M0 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 M0 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 M0 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 3 M0 right", 2, params.mRightKeys);
+        assertEquals("5 key max 3 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 3 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 3 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 3 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4] [5]
+    // |<1> [2] [3]
+    public void testLayout5KeyMax3L0() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_L0);
+        assertEquals("5 key max 3 L0 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 L0 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key max 3 L0 right", 3, params.mRightKeys);
+        assertEquals("5 key max 3 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 3 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key max 3 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 3 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5]
+    // |___ <1> [2] [3]
+    public void testLayout5KeyMax3L1() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_L1);
+        assertEquals("5 key max 3 L1 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 L1 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key max 3 L1 right", 3, params.mRightKeys);
+        assertEquals("5 key max 3 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 3 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key max 3 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 3 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [4] [5]
+    // |___ [3] <1> [2]
+    public void testLayout5KeyMax3L2() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_L2);
+        assertEquals("5 key max 3 L2 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 L2 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 3 L2 right", 2, params.mRightKeys);
+        assertEquals("5 key max 3 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 3 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 3 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 3 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [4]|
+    // [3] [2] <1>|
+    public void testLayout5KeyMax3R0() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_R0);
+        assertEquals("5 key max 3 R0 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 R0 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 R0 left", 2, params.mLeftKeys);
+        assertEquals("5 key max 3 R0 right", 1, params.mRightKeys);
+        assertEquals("5 key max 3 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key max 3 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key max 3 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("5 key max 3 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [4] ___|
+    // [3] [2] <1> ___|
+    public void testLayout5KeyMax3R1() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_R1);
+        assertEquals("5 key max 3 R1 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 R1 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 R1 left", 2, params.mLeftKeys);
+        assertEquals("5 key max 3 R1 right", 1, params.mRightKeys);
+        assertEquals("5 key max 3 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key max 3 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key max 3 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("5 key max 3 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [4] [5]   ___|
+    // [3] <1> [2] ___|
+    public void testLayout5KeyMax3R2() {
+        MoreKeysKeyboardParams params = createParams(5, 3, XPOS_R2);
+        assertEquals("5 key max 3 R2 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 3 R2 rows", 2, params.mNumRows);
+        assertEquals("5 key max 3 R2 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 3 R2 right", 2, params.mRightKeys);
+        assertEquals("5 key max 3 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 3 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 3 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 3 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 3 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 3 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key max 3 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //   [4] [5]
+    // [3] <1> [2]
+    public void testLayout5KeyMax4M0() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_M0);
+        assertEquals("5 key max 4 M0 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 M0 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 4 M0 right", 2, params.mRightKeys);
+        assertEquals("5 key max 4 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 4 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 4 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 4 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4] [5]
+    // |<1> [2] [3]
+    public void testLayout5KeyMax4L0() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_L0);
+        assertEquals("5 key max 4 L0 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 L0 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key max 4 L0 right", 3, params.mRightKeys);
+        assertEquals("5 key max 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key max 4 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5]
+    // |___ <1> [2] [3]
+    public void testLayout5KeyMax4L1() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_L1);
+        assertEquals("5 key max 4 L1 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 L1 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key max 4 L1 right", 3, params.mRightKeys);
+        assertEquals("5 key max 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key max 4 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [4] [5]
+    // |___ [3] <1> [2]
+    public void testLayout5KeyMax4L2() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_L2);
+        assertEquals("5 key max 4 L2 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 L2 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 4 L2 right", 2, params.mRightKeys);
+        assertEquals("5 key max 4 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 4 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 4 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 4 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [4]|
+    // [3] [2] <1>|
+    public void testLayout5KeyMax4R0() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_R0);
+        assertEquals("5 key max 4 R0 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 R0 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 R0 left", 2, params.mLeftKeys);
+        assertEquals("5 key max 4 R0 right", 1, params.mRightKeys);
+        assertEquals("5 key max 4 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key max 4 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key max 4 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("5 key max 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [5] [4] ___|
+    // [3] [2] <1> ___|
+    public void testLayout5KeyMax4R1() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_R1);
+        assertEquals("5 key max 4 R1 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 R1 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 R1 left", 2, params.mLeftKeys);
+        assertEquals("5 key max 4 R1 right", 1, params.mRightKeys);
+        assertEquals("5 key max 4 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key max 4 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key max 4 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("5 key max 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [4] [5]   ___|
+    // [3] <1> [2] ___|
+    public void testLayout5KeyMax4R2() {
+        MoreKeysKeyboardParams params = createParams(5, 4, XPOS_R2);
+        assertEquals("5 key max 4 R2 columns", 3, params.mNumColumns);
+        assertEquals("5 key max 4 R2 rows", 2, params.mNumRows);
+        assertEquals("5 key max 4 R2 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 4 R2 right", 2, params.mRightKeys);
+        assertEquals("5 key max 4 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 4 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 4 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 4 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("5 key max 4 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("5 key max 4 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("5 key max 4 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [3] <1> [2] [4]
+    public void testLayout5KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_M0);
+        assertEquals("5 key max 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 M0 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("5 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("5 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("5 key max 5 M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("5 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4] [5]
+    public void testLayout5KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_L0);
+        assertEquals("5 key max 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 L0 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key max 5 L0 right", 5, params.mRightKeys);
+        assertEquals("5 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key max 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key max 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("5 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout5KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_L1);
+        assertEquals("5 key max 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 L1 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key max 5 L1 right", 5, params.mRightKeys);
+        assertEquals("5 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key max 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key max 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("5 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] <1> [2] [4] [5]
+    public void testLayout5KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_L2);
+        assertEquals("5 key max 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 L2 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key max 5 L2 right", 4, params.mRightKeys);
+        assertEquals("5 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("5 key max 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("5 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [4] [3] [2] <1>|
+    public void testLayout5KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_R0);
+        assertEquals("5 key max 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 R0 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("5 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("5 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key max 5 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("5 key max 5 R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("5 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [4] [3] [2] <1> ___|
+    public void testLayout5KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_R1);
+        assertEquals("5 key max 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 R1 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("5 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("5 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key max 5 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("5 key max 5 R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("5 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [4] [3] <1> [2] ___|
+    public void testLayout5KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(5, 5, XPOS_R2);
+        assertEquals("5 key max 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("5 key max 5 R2 rows", 1, params.mNumRows);
+        assertEquals("5 key max 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("5 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("5 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("5 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key max 5 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("5 key max 5 R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("5 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key max 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [4] [5]
+    // [3] <1> [2]
+    public void testLayout6KeyMax4M0() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_M0);
+        assertEquals("6 key max 4 M0 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 M0 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 M0 left", 1, params.mLeftKeys);
+        assertEquals("6 key max 4 M0 right", 2, params.mRightKeys);
+        assertEquals("6 key max 4 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 4 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key max 4 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 4 M0 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key max 4 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4] [5] [6]
+    // |<1> [2] [3]
+    public void testLayout6KeyMax4L0() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_L0);
+        assertEquals("6 key max 4 L0 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 L0 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 L0 left", 0, params.mLeftKeys);
+        assertEquals("6 key max 4 L0 right", 3, params.mRightKeys);
+        assertEquals("6 key max 4 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 4 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key max 4 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 4 L0 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key max 4 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5] [6]
+    // |___ <1> [2] [3]
+    public void testLayout6KeyMax4L1() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_L1);
+        assertEquals("6 key max 4 L1 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 L1 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 L1 left", 0, params.mLeftKeys);
+        assertEquals("6 key max 4 L1 right", 3, params.mRightKeys);
+        assertEquals("6 key max 4 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 4 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key max 4 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 4 L1 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key max 4 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [4] [5]
+    // |___ [3] <1> [2]
+    public void testLayout6KeyMax4L2() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_L2);
+        assertEquals("6 key max 4 L2 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 L2 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 L2 left", 1, params.mLeftKeys);
+        assertEquals("6 key max 4 L2 right", 2, params.mRightKeys);
+        assertEquals("6 key max 4 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 4 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key max 4 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 4 L2 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key max 4 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [5] [4]|
+    // [3] [2] <1>|
+    public void testLayout6KeyMax4R0() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_R0);
+        assertEquals("6 key max 4 R0 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 R0 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 R0 left", 2, params.mLeftKeys);
+        assertEquals("6 key max 4 R0 right", 1, params.mRightKeys);
+        assertEquals("6 key max 4 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key max 4 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key max 4 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key max 4 R0 [6]", -2, params.getColumnPos(5));
+        assertEquals("6 key max 4 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [5] [4] ___|
+    // [3] [2] <1> ___|
+    public void testLayout6KeyMax4R1() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_R1);
+        assertEquals("6 key max 4 R1 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 R1 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 R1 left", 2, params.mLeftKeys);
+        assertEquals("6 key max 4 R1 right", 1, params.mRightKeys);
+        assertEquals("6 key max 4 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key max 4 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key max 4 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key max 4 R1 [6]", -2, params.getColumnPos(5));
+        assertEquals("6 key max 4 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [4] [5] ___|
+    // [3] <1> [2] ___|
+    public void testLayout6KeyMax4R2() {
+        MoreKeysKeyboardParams params = createParams(6, 4, XPOS_R2);
+        assertEquals("6 key max 4 R2 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 4 R2 rows", 2, params.mNumRows);
+        assertEquals("6 key max 4 R2 left", 1, params.mLeftKeys);
+        assertEquals("6 key max 4 R2 right", 2, params.mRightKeys);
+        assertEquals("6 key max 4 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 4 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 4 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key max 4 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 4 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 4 R2 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key max 4 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 4 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [4] [5]
+    // [3] <1> [2]
+    public void testLayout6KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_M0);
+        assertEquals("6 key max 5 M0 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 M0 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("6 key max 5 M0 right", 2, params.mRightKeys);
+        assertEquals("6 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key max 5 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 5 M0 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[4] [5] [6]
+    // |<1> [2] [3]
+    public void testLayout6KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_L0);
+        assertEquals("6 key max 5 L0 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 L0 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("6 key max 5 L0 right", 3, params.mRightKeys);
+        assertEquals("6 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key max 5 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 5 L0 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5] [6]
+    // |___ <1> [2] [3]
+    public void testLayout6KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_L1);
+        assertEquals("6 key max 5 L1 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 L1 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("6 key max 5 L1 right", 3, params.mRightKeys);
+        assertEquals("6 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key max 5 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 5 L1 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [4] [5]
+    // |___ [3] <1> [2]
+    public void testLayout6KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_L2);
+        assertEquals("6 key max 5 L2 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 L2 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("6 key max 5 L2 right", 2, params.mRightKeys);
+        assertEquals("6 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key max 5 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 5 L2 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [5] [4]|
+    // [3] [2] <1>|
+    public void testLayout6KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_R0);
+        assertEquals("6 key max 5 R0 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 R0 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 R0 left", 2, params.mLeftKeys);
+        assertEquals("6 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("6 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key max 5 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key max 5 R0 [6]", -2, params.getColumnPos(5));
+        assertEquals("6 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [5] [4] ___|
+    // [3] [2] <1> ___|
+    public void testLayout6KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_R1);
+        assertEquals("6 key max 5 R1 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 R1 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 R1 left", 2, params.mLeftKeys);
+        assertEquals("6 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("6 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key max 5 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key max 5 R1 [6]", -2, params.getColumnPos(5));
+        assertEquals("6 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [4] [5] ___|
+    // [3] <1> [2] ___|
+    public void testLayout6KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(6, 5, XPOS_R2);
+        assertEquals("6 key max 5 R2 columns", 3, params.mNumColumns);
+        assertEquals("6 key max 5 R2 rows", 2, params.mNumRows);
+        assertEquals("6 key max 5 R2 left", 1, params.mLeftKeys);
+        assertEquals("6 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("6 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("6 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key max 5 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key max 5 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key max 5 R2 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key max 5 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |<1> [2] [3] [4] [5] [6] [7] ___ ___ ___|
+    public void testLayout7KeyMax7L0() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L0);
+        assertEquals("7 key max 7 L0 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 L0 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key max 7 L0 right", 7, params.mRightKeys);
+        assertEquals("7 key max 7 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key max 7 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key max 7 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("7 key max 7 L0 [6]", 5, params.getColumnPos(5));
+        assertEquals("7 key max 7 L0 [7]", 6, params.getColumnPos(6));
+        assertEquals("7 key max 7 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ <1> [2] [3] [4] [5] [6] [7] ___ ___|
+    public void testLayout7KeyMax7L1() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L1);
+        assertEquals("7 key max 7 L1 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 L1 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key max 7 L1 right", 7, params.mRightKeys);
+        assertEquals("7 key max 7 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key max 7 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key max 7 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("7 key max 7 L1 [6]", 5, params.getColumnPos(5));
+        assertEquals("7 key max 7 L1 [7]", 6, params.getColumnPos(6));
+        assertEquals("7 key max 7 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [3] <1> [2] [4] [5] [6] [7] ___ ___|
+    public void testLayout7KeyMax7L2() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L2);
+        assertEquals("7 key max 7 L2 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 L2 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key max 7 L2 right", 6, params.mRightKeys);
+        assertEquals("7 key max 7 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 7 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 7 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("7 key max 7 L2 [6]", 4, params.getColumnPos(5));
+        assertEquals("7 key max 7 L2 [7]", 5, params.getColumnPos(6));
+        assertEquals("7 key max 7 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [3] <1> [2] [4] [6] [7] ___ ___|
+    public void testLayout7KeyMax7L3() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_L3);
+        assertEquals("7 key max 7 L3 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 L3 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 L3 left", 2, params.mLeftKeys);
+        assertEquals("7 key max 7 L3 right", 5, params.mRightKeys);
+        assertEquals("7 key max 7 L3 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 L3 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 L3 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 7 L3 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 7 L3 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key max 7 L3 [6]", 3, params.getColumnPos(5));
+        assertEquals("7 key max 7 L3 [7]", 4, params.getColumnPos(6));
+        assertEquals("7 key max 7 L3 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 L3 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [7] [5] [3] <1> [2] [4] [6] ___ ___|
+    public void testLayout7KeyMax7M0() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_M0);
+        assertEquals("7 key max 7 M0 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 M0 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 M0 left", 3, params.mLeftKeys);
+        assertEquals("7 key max 7 M0 right", 4, params.mRightKeys);
+        assertEquals("7 key max 7 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 7 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 7 M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key max 7 M0 [6]", 3, params.getColumnPos(5));
+        assertEquals("7 key max 7 M0 [7]", -3, params.getColumnPos(6));
+        assertEquals("7 key max 7 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 M0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [7] [5] [3] <1> [2] [4] [6] ___|
+    public void testLayout7KeyMax7M1() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_M1);
+        assertEquals("7 key max 7 M1 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 M1 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 M1 left", 3, params.mLeftKeys);
+        assertEquals("7 key max 7 M1 right", 4, params.mRightKeys);
+        assertEquals("7 key max 7 M1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 M1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 M1 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 7 M1 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 7 M1 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key max 7 M1 [6]", 3, params.getColumnPos(5));
+        assertEquals("7 key max 7 M1 [7]", -3, params.getColumnPos(6));
+        assertEquals("7 key max 7 M1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 M1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [7] [6] [5] [3] <1> [2] [4] ___|
+    public void testLayout7KeyMax7R3() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R3);
+        assertEquals("7 key max 7 R3 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 R3 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 R3 left", 4, params.mLeftKeys);
+        assertEquals("7 key max 7 R3 right", 3, params.mRightKeys);
+        assertEquals("7 key max 7 R3 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 R3 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 R3 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 7 R3 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 7 R3 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key max 7 R3 [6]", -3, params.getColumnPos(5));
+        assertEquals("7 key max 7 R3 [7]", -4, params.getColumnPos(6));
+        assertEquals("7 key max 7 R3 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 R3 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [7] [6] [5] [4] [3] <1> [2] ___|
+    public void testLayout7KeyMax7R2() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R2);
+        assertEquals("7 key max 7 R2 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 R2 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 R2 left", 5, params.mLeftKeys);
+        assertEquals("7 key max 7 R2 right", 2, params.mRightKeys);
+        assertEquals("7 key max 7 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 7 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 7 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("7 key max 7 R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("7 key max 7 R2 [6]", -4, params.getColumnPos(5));
+        assertEquals("7 key max 7 R2 [7]", -5, params.getColumnPos(6));
+        assertEquals("7 key max 7 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 R2 default", WIDTH * 5, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [7] [6] [5] [4] [3] [2] <1> ___|
+    public void testLayout7KeyMax7R1() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R1);
+        assertEquals("7 key max 7 R1 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 R1 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 R1 left", 6, params.mLeftKeys);
+        assertEquals("7 key max 7 R1 right", 1, params.mRightKeys);
+        assertEquals("7 key max 7 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key max 7 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key max 7 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key max 7 R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("7 key max 7 R1 [6]", -5, params.getColumnPos(5));
+        assertEquals("7 key max 7 R1 [7]", -6, params.getColumnPos(6));
+        assertEquals("7 key max 7 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 R1 default", WIDTH * 6, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [7] [6] [5] [4] [3] [2] <1>|
+    public void testLayout7KeyMax7R0() {
+        MoreKeysKeyboardParams params = createParams(7, 7, XPOS_R0);
+        assertEquals("7 key max 7 R0 columns", 7, params.mNumColumns);
+        assertEquals("7 key max 7 R0 rows", 1, params.mNumRows);
+        assertEquals("7 key max 7 R0 left", 6, params.mLeftKeys);
+        assertEquals("7 key max 7 R0 right", 1, params.mRightKeys);
+        assertEquals("7 key max 7 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 7 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key max 7 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key max 7 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key max 7 R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("7 key max 7 R0 [6]", -5, params.getColumnPos(5));
+        assertEquals("7 key max 7 R0 [7]", -6, params.getColumnPos(6));
+        assertEquals("7 key max 7 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 7 R0 default", WIDTH * 6, params.getDefaultKeyCoordX());
+    }
+
+    //   [5] [6] [7]
+    // [3] <1> [2] [4]
+    public void testLayout7KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_M0);
+        assertEquals("7 key max 5 M0 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 M0 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("7 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("7 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 5 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key max 5 M0 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key max 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5] [6] [7]
+    // |<1> [2] [3] [4]
+    public void testLayout7KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_L0);
+        assertEquals("7 key max 5 L0 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 L0 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key max 5 L0 right", 4, params.mRightKeys);
+        assertEquals("7 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key max 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key max 5 L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key max 5 L0 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7]
+    // |___ <1> [2] [3] [4]
+    public void testLayout7KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_L1);
+        assertEquals("7 key max 5 L1 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 L1 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key max 5 L1 right", 4, params.mRightKeys);
+        assertEquals("7 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key max 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key max 5 L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key max 5 L1 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [5] [6] [7]
+    // |___ [3] <1> [2] [4]
+    public void testLayout7KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_L2);
+        assertEquals("7 key max 5 L2 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 L2 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key max 5 L2 right", 3, params.mRightKeys);
+        assertEquals("7 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key max 5 L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 L2 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key max 5 L2 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key max 5 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [7] [6] [5]|
+    // [4] [3] [2] <1>|
+    public void testLayout7KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_R0);
+        assertEquals("7 key max 5 R0 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 R0 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 R0 left", 3, params.mLeftKeys);
+        assertEquals("7 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("7 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key max 5 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key max 5 R0 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key max 5 R0 [7]", -2, params.getColumnPos(6));
+        assertEquals("7 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //     [7] [6] [5] ___|
+    // [4] [3] [2] <1> ___|
+    public void testLayout7KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_R1);
+        assertEquals("7 key max 5 R1 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 R1 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 R1 left", 3, params.mLeftKeys);
+        assertEquals("7 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("7 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key max 5 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key max 5 R1 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key max 5 R1 [7]", -2, params.getColumnPos(6));
+        assertEquals("7 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //   [7] [5] [6]   ___|
+    // [4] [3] <1> [2] ___|
+    public void testLayout7KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(7, 5, XPOS_R2);
+        assertEquals("7 key max 5 R2 columns", 4, params.mNumColumns);
+        assertEquals("7 key max 5 R2 rows", 2, params.mNumRows);
+        assertEquals("7 key max 5 R2 left", 2, params.mLeftKeys);
+        assertEquals("7 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("7 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 5 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("7 key max 5 R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key max 5 R2 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key max 5 R2 [7]", -1, params.getColumnPos(6));
+        assertEquals("7 key max 5 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key max 5 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [7]
+    // [6] [4] [5]
+    // [3] <1> [2]
+    public void testLayout7KeyMax3M0() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_M0);
+        assertEquals("7 key max 3 M0 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 M0 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 M0 left", 1, params.mLeftKeys);
+        assertEquals("7 key max 3 M0 right", 2, params.mRightKeys);
+        assertEquals("7 key max 3 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 3 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 3 M0 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 M0 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key max 3 M0 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key max 3 M0 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[7]
+    // |[4] [5] [6]
+    // |<1> [2] [3]
+    public void testLayout7KeyMax3L0() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_L0);
+        assertEquals("7 key max 3 L0 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 L0 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key max 3 L0 right", 3, params.mRightKeys);
+        assertEquals("7 key max 3 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 3 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key max 3 L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key max 3 L0 [6]", 2, params.getColumnPos(5));
+        assertEquals("7 key max 3 L0 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [7]
+    // |___ [4] [5] [6]
+    // |___ <1> [2] [3]
+    public void testLayout7KeyMax3L1() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_L1);
+        assertEquals("7 key max 3 L1 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 L1 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key max 3 L1 right", 3, params.mRightKeys);
+        assertEquals("7 key max 3 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 3 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key max 3 L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key max 3 L1 [6]", 2, params.getColumnPos(5));
+        assertEquals("7 key max 3 L1 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___     [7]
+    // |___ [6] [4] [5]
+    // |___ [3] <1> [2]
+    public void testLayout7KeyMax3L2() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_L2);
+        assertEquals("7 key max 3 L2 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 L2 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key max 3 L2 right", 2, params.mRightKeys);
+        assertEquals("7 key max 3 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 3 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 3 L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key max 3 L2 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key max 3 L2 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //         [7]|
+    // [6] [5] [4]|
+    // [3] [2] <1>|
+    public void testLayout7KeyMax3R0() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_R0);
+        assertEquals("7 key max 3 R0 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 R0 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 R0 left", 2, params.mLeftKeys);
+        assertEquals("7 key max 3 R0 right", 1, params.mRightKeys);
+        assertEquals("7 key max 3 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key max 3 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key max 3 R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("7 key max 3 R0 [6]", -2, params.getColumnPos(5));
+        assertEquals("7 key max 3 R0 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //         [7] ___|
+    // [6] [5] [4] ___|
+    // [3] [2] <1> ___|
+    public void testLayout7KeyMax3R1() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_R1);
+        assertEquals("7 key max 3 R1 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 R1 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 R1 left", 2, params.mLeftKeys);
+        assertEquals("7 key max 3 R1 right", 1, params.mRightKeys);
+        assertEquals("7 key max 3 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key max 3 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key max 3 R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("7 key max 3 R1 [6]", -2, params.getColumnPos(5));
+        assertEquals("7 key max 3 R1 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //     [7]     ___|
+    // [6] [4] [5] ___|
+    // [3] <1> [2] ___|
+    public void testLayout7KeyMax3R2() {
+        MoreKeysKeyboardParams params = createParams(7, 3, XPOS_R2);
+        assertEquals("7 key max 3 R2 columns", 3, params.mNumColumns);
+        assertEquals("7 key max 3 R2 rows", 3, params.mNumRows);
+        assertEquals("7 key max 3 R2 left", 1, params.mLeftKeys);
+        assertEquals("7 key max 3 R2 right", 2, params.mRightKeys);
+        assertEquals("7 key max 3 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("7 key max 3 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key max 3 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key max 3 R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("7 key max 3 R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("7 key max 3 R2 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key max 3 R2 [7]", 0, params.getColumnPos(6));
+        assertEquals("7 key max 3 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key max 3 R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [7] [5] [6] [8]
+    // [3] <1> [2] [4]
+    public void testLayout8KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_M0);
+        assertEquals("8 key max 5 M0 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 M0 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("8 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("8 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key max 5 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key max 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key max 5 M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5] [6] [7] [8]
+    // |<1> [2] [3] [4]
+    public void testLayout8KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_L0);
+        assertEquals("8 key max 5 L0 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 L0 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("8 key max 5 L0 right", 4, params.mRightKeys);
+        assertEquals("8 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key max 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key max 5 L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key max 5 L0 [7]", 2, params.getColumnPos(6));
+        assertEquals("8 key max 5 L0 [8]", 3, params.getColumnPos(7));
+        assertEquals("8 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7] [8]
+    // |___ <1> [2] [3] [4]
+    public void testLayout8KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_L1);
+        assertEquals("8 key max 5 L1 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 L1 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("8 key max 5 L1 right", 4, params.mRightKeys);
+        assertEquals("8 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key max 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key max 5 L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key max 5 L1 [7]", 2, params.getColumnPos(6));
+        assertEquals("8 key max 5 L1 [8]", 3, params.getColumnPos(7));
+        assertEquals("8 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [7] [5] [6] [8]
+    // |___ [3] <1> [2] [4]
+    public void testLayout8KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_L2);
+        assertEquals("8 key max 5 L2 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 L2 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("8 key max 5 L2 right", 3, params.mRightKeys);
+        assertEquals("8 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key max 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key max 5 L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 L2 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key max 5 L2 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key max 5 L2 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [8] [7] [6] [5]|
+    // [4] [3] [2] <1>|
+    public void testLayout8KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_R0);
+        assertEquals("8 key max 5 R0 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 R0 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 R0 left", 3, params.mLeftKeys);
+        assertEquals("8 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("8 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("8 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("8 key max 5 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("8 key max 5 R0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key max 5 R0 [7]", -2, params.getColumnPos(6));
+        assertEquals("8 key max 5 R0 [8]", -3, params.getColumnPos(7));
+        assertEquals("8 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [8] [7] [6] [5] ___|
+    // [4] [3] [2] <1> ___|
+    public void testLayout8KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_R1);
+        assertEquals("8 key max 5 R1 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 R1 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 R1 left", 3, params.mLeftKeys);
+        assertEquals("8 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("8 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("8 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("8 key max 5 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("8 key max 5 R1 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key max 5 R1 [7]", -2, params.getColumnPos(6));
+        assertEquals("8 key max 5 R1 [8]", -3, params.getColumnPos(7));
+        assertEquals("8 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [8] [7] [5] [6] ___|
+    // [4] [3] <1> [2] ___|
+    public void testLayout8KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(8, 5, XPOS_R2);
+        assertEquals("8 key max 5 R2 columns", 4, params.mNumColumns);
+        assertEquals("8 key max 5 R2 rows", 2, params.mNumRows);
+        assertEquals("8 key max 5 R2 left", 2, params.mLeftKeys);
+        assertEquals("8 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("8 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("8 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key max 5 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("8 key max 5 R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key max 5 R2 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key max 5 R2 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key max 5 R2 [8]", -2, params.getColumnPos(7));
+        assertEquals("8 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key max 5 R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [8] [6] [7] [9]
+    // [5] [3] <1> [2] [4]
+    public void testLayout9KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_M0);
+        assertEquals("9 key max 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 M0 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("9 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("9 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("9 key max 5 M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("9 key max 5 M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key max 5 M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key max 5 M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("9 key max 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7] [8] [9]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout9KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_L0);
+        assertEquals("9 key max 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 L0 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("9 key max 5 L0 right", 5, params.mRightKeys);
+        assertEquals("9 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("9 key max 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("9 key max 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("9 key max 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key max 5 L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key max 5 L0 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8] [9]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout9KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_L1);
+        assertEquals("9 key max 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 L1 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("9 key max 5 L1 right", 5, params.mRightKeys);
+        assertEquals("9 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("9 key max 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("9 key max 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("9 key max 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key max 5 L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key max 5 L1 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key max 5 L1 adjust",0, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___   [6] [7] [8] [9]
+    // |___ [3] <1> [2] [4] [5]
+    public void testLayout9KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_L2);
+        assertEquals("9 key max 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 L2 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("9 key max 5 L2 right", 4, params.mRightKeys);
+        assertEquals("9 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key max 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("9 key max 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("9 key max 5 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key max 5 L2 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key max 5 L2 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key max 5 L2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [9] [8] [7] [6]|
+    // [5] [4] [3] [2] <1>|
+    public void testLayout9KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_R0);
+        assertEquals("9 key max 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 R0 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("9 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("9 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("9 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("9 key max 5 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("9 key max 5 R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("9 key max 5 R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 R0 [7]", -1, params.getColumnPos(6));
+        assertEquals("9 key max 5 R0 [8]", -2, params.getColumnPos(7));
+        assertEquals("9 key max 5 R0 [9]", -3, params.getColumnPos(8));
+        assertEquals("9 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //     [9] [8] [7] [6] ___|
+    // [5] [4] [3] [2] <1> ___|
+    public void testLayout9KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_R1);
+        assertEquals("9 key max 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 R1 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("9 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("9 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("9 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("9 key max 5 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("9 key max 5 R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("9 key max 5 R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 R1 [7]", -1, params.getColumnPos(6));
+        assertEquals("9 key max 5 R1 [8]", -2, params.getColumnPos(7));
+        assertEquals("9 key max 5 R1 [9]", -3, params.getColumnPos(8));
+        assertEquals("9 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //   [9] [8] [6] [7]   ___|
+    // [5] [4] [3] <1> [2] ___|
+    public void testLayout9KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(9, 5, XPOS_R2);
+        assertEquals("9 key max 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("9 key max 5 R2 rows", 2, params.mNumRows);
+        assertEquals("9 key max 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("9 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("9 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("9 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key max 5 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("9 key max 5 R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("9 key max 5 R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key max 5 R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key max 5 R2 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key max 5 R2 [9]", -2, params.getColumnPos(8));
+        assertEquals("9 key max 5 R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key max 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [8] [6] [7] [9]
+    // [5] [3] <1> [2] [4]
+    public void testLayout10KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_M0);
+        assertEquals("10 key max 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 M0 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("10 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("10 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("10 key max 5 M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("10 key max 5 M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key max 5 M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key max 5 M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("10 key max 5 M0 [A]", -2, params.getColumnPos(9));
+        assertEquals("10 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7] [8] [9] [A]
+    // |<1> [2] [3] [4] [5]
+    public void testLayout10KeyMax5L0() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_L0);
+        assertEquals("10 key max 5 L0 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 L0 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 L0 left", 0, params.mLeftKeys);
+        assertEquals("10 key max 5 L0 right", 5, params.mRightKeys);
+        assertEquals("10 key max 5 L0 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key max 5 L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("10 key max 5 L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("10 key max 5 L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("10 key max 5 L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key max 5 L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("10 key max 5 L0 [9]", 3, params.getColumnPos(8));
+        assertEquals("10 key max 5 L0 [A]", 4, params.getColumnPos(9));
+        assertEquals("10 key max 5 L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8] [9] [A]
+    // |___ <1> [2] [3] [4] [5]
+    public void testLayout10KeyMax5L1() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_L1);
+        assertEquals("10 key max 5 L1 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 L1 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 L1 left", 0, params.mLeftKeys);
+        assertEquals("10 key max 5 L1 right", 5, params.mRightKeys);
+        assertEquals("10 key max 5 L1 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key max 5 L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("10 key max 5 L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("10 key max 5 L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("10 key max 5 L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key max 5 L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("10 key max 5 L1 [9]", 3, params.getColumnPos(8));
+        assertEquals("10 key max 5 L1 [A]", 4, params.getColumnPos(9));
+        assertEquals("10 key max 5 L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [8] [6] [7] [9] [A]
+    // |___ [3] <1> [2] [4] [5]
+    public void testLayout10KeyMax5L2() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_L2);
+        assertEquals("10 key max 5 L2 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 L2 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 L2 left", 1, params.mLeftKeys);
+        assertEquals("10 key max 5 L2 right", 4, params.mRightKeys);
+        assertEquals("10 key max 5 L2 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key max 5 L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key max 5 L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("10 key max 5 L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("10 key max 5 L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key max 5 L2 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key max 5 L2 [9]", 2, params.getColumnPos(8));
+        assertEquals("10 key max 5 L2 [A]", 3, params.getColumnPos(9));
+        assertEquals("10 key max 5 L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [9] [8] [7] [6]|
+    // [5] [4] [3] [2] <1>|
+    public void testLayout10KeyMax5R0() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_R0);
+        assertEquals("10 key max 5 R0 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 R0 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 R0 left", 4, params.mLeftKeys);
+        assertEquals("10 key max 5 R0 right", 1, params.mRightKeys);
+        assertEquals("10 key max 5 R0 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("10 key max 5 R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("10 key max 5 R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("10 key max 5 R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("10 key max 5 R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 R0 [7]", -1, params.getColumnPos(6));
+        assertEquals("10 key max 5 R0 [8]", -2, params.getColumnPos(7));
+        assertEquals("10 key max 5 R0 [9]", -3, params.getColumnPos(8));
+        assertEquals("10 key max 5 R0 [A]", -4, params.getColumnPos(9));
+        assertEquals("10 key max 5 R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [9] [8] [7] [6] ___|
+    // [5] [4] [3] [2] <1> ___|
+    public void testLayout10KeyMax5R1() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_R1);
+        assertEquals("10 key max 5 R1 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 R1 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 R1 left", 4, params.mLeftKeys);
+        assertEquals("10 key max 5 R1 right", 1, params.mRightKeys);
+        assertEquals("10 key max 5 R1 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("10 key max 5 R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("10 key max 5 R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("10 key max 5 R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("10 key max 5 R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 R1 [7]", -1, params.getColumnPos(6));
+        assertEquals("10 key max 5 R1 [8]", -2, params.getColumnPos(7));
+        assertEquals("10 key max 5 R1 [9]", -3, params.getColumnPos(8));
+        assertEquals("10 key max 5 R1 [A]", -4, params.getColumnPos(9));
+        assertEquals("10 key max 5 R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [9] [8] [6] [7] ___|
+    // [5] [4] [3] <1> [2] ___|
+    public void testLayout10KeyMax5R2() {
+        MoreKeysKeyboardParams params = createParams(10, 5, XPOS_R2);
+        assertEquals("10 key max 5 R2 columns", 5, params.mNumColumns);
+        assertEquals("10 key max 5 R2 rows", 2, params.mNumRows);
+        assertEquals("10 key max 5 R2 left", 3, params.mLeftKeys);
+        assertEquals("10 key max 5 R2 right", 2, params.mRightKeys);
+        assertEquals("10 key max 5 R2 <1>", 0, params.getColumnPos(0));
+        assertEquals("10 key max 5 R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key max 5 R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key max 5 R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("10 key max 5 R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("10 key max 5 R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key max 5 R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key max 5 R2 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key max 5 R2 [9]", -2, params.getColumnPos(8));
+        assertEquals("10 key max 5 R2 [A]", -3, params.getColumnPos(9));
+        assertEquals("10 key max 5 R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key max 5 R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //   [9] [A] [B]
+    // [7] [5] [6] [8]
+    // [3] <1> [2] [4]
+    public void testLayout11KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(11, 5, XPOS_M0);
+        assertEquals("11 key max 5 M0 columns", 4, params.mNumColumns);
+        assertEquals("11 key max 5 M0 rows", 3, params.mNumRows);
+        assertEquals("11 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("11 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("11 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("11 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("11 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("11 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("11 key max 5 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("11 key max 5 M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("11 key max 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("11 key max 5 M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("11 key max 5 M0 [9]", 0, params.getColumnPos(8));
+        assertEquals("11 key max 5 M0 [A]", 1, params.getColumnPos(9));
+        assertEquals("11 key max 5 M0 [B]", 2, params.getColumnPos(10));
+        assertEquals("11 key max 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("11 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [B] [9] [A] [C]
+    // [7] [5] [6] [8]
+    // [3] <1> [2] [4]
+    public void testLayout12KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(12, 5, XPOS_M0);
+        assertEquals("12 key max 5 M0 columns", 4, params.mNumColumns);
+        assertEquals("12 key max 5 M0 rows", 3, params.mNumRows);
+        assertEquals("12 key max 5 M0 left", 1, params.mLeftKeys);
+        assertEquals("12 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("12 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("12 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("12 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("12 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("12 key max 5 M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("12 key max 5 M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("12 key max 5 M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("12 key max 5 M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("12 key max 5 M0 [9]", 0, params.getColumnPos(8));
+        assertEquals("12 key max 5 M0 [A]", 1, params.getColumnPos(9));
+        assertEquals("12 key max 5 M0 [B]", -1, params.getColumnPos(10));
+        assertEquals("12 key max 5 M0 [C]", 2, params.getColumnPos(11));
+        assertEquals("12 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("12 key max 5 M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [D] [B] [C]
+    // [A] [8] [6] [7] [9]
+    // [5] [3] <1> [2] [4]
+    public void testLayout13KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(13, 5, XPOS_M0);
+        assertEquals("13 key max 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("13 key max 5 M0 rows", 3, params.mNumRows);
+        assertEquals("13 key max 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("13 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("13 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("13 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("13 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("13 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("13 key max 5 M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("13 key max 5 M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("13 key max 5 M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("13 key max 5 M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("13 key max 5 M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("13 key max 5 M0 [A]", -2, params.getColumnPos(9));
+        assertEquals("13 key max 5 M0 [B]", 0, params.getColumnPos(10));
+        assertEquals("13 key max 5 M0 [C]", 1, params.getColumnPos(11));
+        assertEquals("13 key max 5 M0 [D]", -1, params.getColumnPos(12));
+        assertEquals("13 key max 5 M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("13 key max 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    //   [D] [B] [C] [E]
+    // [A] [8] [6] [7] [9]
+    // [5] [3] <1> [2] [4]
+    public void testLayout14KeyMax5M0() {
+        MoreKeysKeyboardParams params = createParams(14, 5, XPOS_M0);
+        assertEquals("13 key max 5 M0 columns", 5, params.mNumColumns);
+        assertEquals("13 key max 5 M0 rows", 3, params.mNumRows);
+        assertEquals("13 key max 5 M0 left", 2, params.mLeftKeys);
+        assertEquals("13 key max 5 M0 right", 3, params.mRightKeys);
+        assertEquals("13 key max 5 M0 <1>", 0, params.getColumnPos(0));
+        assertEquals("13 key max 5 M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("13 key max 5 M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("13 key max 5 M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("13 key max 5 M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("13 key max 5 M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("13 key max 5 M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("13 key max 5 M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("13 key max 5 M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("13 key max 5 M0 [A]", -2, params.getColumnPos(9));
+        assertEquals("13 key max 5 M0 [B]", 0, params.getColumnPos(10));
+        assertEquals("13 key max 5 M0 [C]", 1, params.getColumnPos(11));
+        assertEquals("13 key max 5 M0 [D]", -1, params.getColumnPos(12));
+        assertEquals("13 key max 5 M0 [E]", 2, params.getColumnPos(13));
+        assertEquals("13 key max 5 M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("13 key max 5 M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
new file mode 100644
index 0000000..e090031
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -0,0 +1,303 @@
+/*
+ * 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.keyboard.internal;
+
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+
+import com.android.inputmethod.latin.tests.R;
+
+import java.util.Arrays;
+
+public class KeySpecParserCsvTests extends AndroidTestCase {
+    private Resources mTestResources;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestResources = getTestContext().getResources();
+    }
+
+    private static String format(String message, Object expected, Object actual) {
+        return message + " expected:<" + expected + "> but was:<" + actual + ">";
+    }
+
+    private void assertTextArray(String message, String value, String ... expected) {
+        final String actual[] = KeySpecParser.parseCsvString(value, mTestResources,
+                R.string.empty_string);
+        if (expected.length == 0) {
+            assertNull(message + ": expected=null actual=" + Arrays.toString(actual),
+                    actual);
+            return;
+        }
+        assertEquals(message + ": expected=" + Arrays.toString(expected)
+                + " actual=" + Arrays.toString(actual)
+                + ": result length", expected.length, actual.length);
+        for (int i = 0; i < actual.length; i++) {
+            final boolean equals = TextUtils.equals(expected[i], actual[i]);
+            assertTrue(format(message + ": result at " + i + ":", expected[i], actual[i]), equals);
+        }
+    }
+
+    private void assertError(String message, String value, String ... expected) {
+        try {
+            assertTextArray(message, value, expected);
+            fail(message);
+        } catch (Exception pcpe) {
+            // success.
+        }
+    }
+
+    // \U001d11e: MUSICAL SYMBOL G CLEF
+    private static final String PAIR1 = "\ud834\udd1e";
+    // \U001d122: MUSICAL SYMBOL F CLEF
+    private static final String PAIR2 = "\ud834\udd22";
+    // \U002f8a6: CJK COMPATIBILITY IDEOGRAPH-2F8A6; variant character of \u6148.
+    private static final String PAIR3 = "\ud87e\udca6";
+    private static final String SURROGATE1 = PAIR1 + PAIR2;
+    private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3;
+
+    public void testParseCsvTextZero() {
+        assertTextArray("Empty string", "");
+        assertTextArray("Empty entry", ",");
+        assertTextArray("Empty entry at beginning", ",a", "a");
+        assertTextArray("Empty entry at end", "a,", "a");
+        assertTextArray("Empty entry at middle", "a,,b", "a", "b");
+        assertTextArray("Empty entries with escape", ",a,b\\,c,,d,", "a", "b\\,c", "d");
+    }
+
+    public void testParseCsvTextSingle() {
+        assertTextArray("Single char", "a", "a");
+        assertTextArray("Surrogate pair", PAIR1, PAIR1);
+        assertTextArray("Single escape", "\\", "\\");
+        assertTextArray("Space", " ", " ");
+        assertTextArray("Single label", "abc", "abc");
+        assertTextArray("Single surrogate pairs label", SURROGATE2, SURROGATE2);
+        assertTextArray("Spaces", "   ", "   ");
+        assertTextArray("Spaces in label", "a b c", "a b c");
+        assertTextArray("Spaces at beginning of label", " abc", " abc");
+        assertTextArray("Spaces at end of label", "abc ", "abc ");
+        assertTextArray("Label surrounded by spaces", " abc ", " abc ");
+        assertTextArray("Surrogate pair surrounded by space",
+                " " + PAIR1 + " ",
+                " " + PAIR1 + " ");
+        assertTextArray("Surrogate pair within characters",
+                "ab" + PAIR2 + "cd",
+                "ab" + PAIR2 + "cd");
+        assertTextArray("Surrogate pairs within characters",
+                "ab" + SURROGATE1 + "cd",
+                "ab" + SURROGATE1 + "cd");
+
+        assertTextArray("Incomplete resource reference 1", "string", "string");
+        assertTextArray("Incomplete resource reference 2", "@string", "@string");
+        assertTextArray("Incomplete resource reference 3", "string/", "string/");
+        assertTextArray("Incomplete resource reference 4", "@" + SURROGATE2, "@" + SURROGATE2);
+    }
+
+    public void testParseCsvTextSingleEscaped() {
+        assertTextArray("Escaped char", "\\a", "\\a");
+        assertTextArray("Escaped surrogate pair", "\\" + PAIR1, "\\" + PAIR1);
+        assertTextArray("Escaped comma", "\\,", "\\,");
+        assertTextArray("Escaped comma escape", "a\\,\\", "a\\,\\");
+        assertTextArray("Escaped escape", "\\\\", "\\\\");
+        assertTextArray("Escaped label", "a\\bc", "a\\bc");
+        assertTextArray("Escaped surrogate", "a\\" + PAIR1 + "c", "a\\" + PAIR1 + "c");
+        assertTextArray("Escaped label at beginning", "\\abc", "\\abc");
+        assertTextArray("Escaped surrogate at beginning", "\\" + SURROGATE2, "\\" + SURROGATE2);
+        assertTextArray("Escaped label at end", "abc\\", "abc\\");
+        assertTextArray("Escaped surrogate at end", SURROGATE2 + "\\", SURROGATE2 + "\\");
+        assertTextArray("Escaped label with comma", "a\\,c", "a\\,c");
+        assertTextArray("Escaped surrogate with comma",
+                PAIR1 + "\\," + PAIR2, PAIR1 + "\\," + PAIR2);
+        assertTextArray("Escaped label with comma at beginning", "\\,bc", "\\,bc");
+        assertTextArray("Escaped surrogate with comma at beginning",
+                "\\," + SURROGATE1, "\\," + SURROGATE1);
+        assertTextArray("Escaped label with comma at end", "ab\\,", "ab\\,");
+        assertTextArray("Escaped surrogate with comma at end",
+                SURROGATE2 + "\\,", SURROGATE2 + "\\,");
+        assertTextArray("Escaped label with successive", "\\,\\\\bc", "\\,\\\\bc");
+        assertTextArray("Escaped surrogate with successive",
+                "\\,\\\\" + SURROGATE1, "\\,\\\\" + SURROGATE1);
+        assertTextArray("Escaped label with escape", "a\\\\c", "a\\\\c");
+        assertTextArray("Escaped surrogate with escape",
+                PAIR1 + "\\\\" + PAIR2, PAIR1 + "\\\\" + PAIR2);
+
+        assertTextArray("Escaped @string", "\\@string", "\\@string");
+        assertTextArray("Escaped @string/", "\\@string/", "\\@string/");
+        assertTextArray("Escaped @string/", "\\@string/empty_string", "\\@string/empty_string");
+    }
+
+    public void testParseCsvTextMulti() {
+        assertTextArray("Multiple chars", "a,b,c", "a", "b", "c");
+        assertTextArray("Multiple chars", "a,b,\\c", "a", "b", "\\c");
+        assertTextArray("Multiple chars and escape at beginning and end",
+                "\\a,b,\\c\\", "\\a", "b", "\\c\\");
+        assertTextArray("Multiple surrogates", PAIR1 + "," + PAIR2 + "," + PAIR3,
+                PAIR1, PAIR2, PAIR3);
+        assertTextArray("Multiple chars surrounded by spaces", " a , b , c ", " a ", " b ", " c ");
+        assertTextArray("Multiple labels", "abc,def,ghi", "abc", "def", "ghi");
+        assertTextArray("Multiple surrogated", SURROGATE1 + "," + SURROGATE2,
+                SURROGATE1, SURROGATE2);
+        assertTextArray("Multiple labels surrounded by spaces", " abc , def , ghi ",
+                " abc ", " def ", " ghi ");
+    }
+
+    public void testParseCsvTextMultiEscaped() {
+        assertTextArray("Multiple chars with comma", "a,\\,,c", "a", "\\,", "c");
+        assertTextArray("Multiple chars with comma surrounded by spaces", " a , \\, , c ",
+                " a ", " \\, ", " c ");
+        assertTextArray("Multiple labels with escape",
+                "\\abc,d\\ef,gh\\i", "\\abc", "d\\ef", "gh\\i");
+        assertTextArray("Multiple labels with escape surrounded by spaces",
+                " \\abc , d\\ef , gh\\i ", " \\abc ", " d\\ef ", " gh\\i ");
+        assertTextArray("Multiple labels with comma and escape",
+                "ab\\\\,d\\\\\\,,g\\,i", "ab\\\\", "d\\\\\\,", "g\\,i");
+        assertTextArray("Multiple labels with comma and escape surrounded by spaces",
+                " ab\\\\ , d\\\\\\, , g\\,i ", " ab\\\\ ", " d\\\\\\, ", " g\\,i ");
+
+        assertTextArray("Multiple escaped @string", "\\@,\\@string/empty_string",
+                "\\@", "\\@string/empty_string");
+    }
+
+    public void testParseCsvResourceError() {
+        assertError("Incomplete resource name", "@string/", "@string/");
+        assertError("Non existing resource", "@string/non_existing");
+    }
+
+    public void testParseCsvResourceZero() {
+        assertTextArray("Empty string",
+                "@string/empty_string");
+    }
+
+    public void testParseCsvResourceSingle() {
+        assertTextArray("Single char",
+                "@string/single_char", "a");
+        assertTextArray("Space",
+                "@string/space", " ");
+        assertTextArray("Single label",
+                "@string/single_label", "abc");
+        assertTextArray("Spaces",
+                "@string/spaces", "   ");
+        assertTextArray("Spaces in label",
+                "@string/spaces_in_label", "a b c");
+        assertTextArray("Spaces at beginning of label",
+                "@string/spaces_at_beginning_of_label", " abc");
+        assertTextArray("Spaces at end of label",
+                "@string/spaces_at_end_of_label", "abc ");
+        assertTextArray("label surrounded by spaces",
+                "@string/label_surrounded_by_spaces", " abc ");
+
+        assertTextArray("Escape and single char",
+                "\\\\@string/single_char", "\\\\a");
+    }
+
+    public void testParseCsvResourceSingleEscaped() {
+        assertTextArray("Escaped char",
+                "@string/escaped_char", "\\a");
+        assertTextArray("Escaped comma",
+                "@string/escaped_comma", "\\,");
+        assertTextArray("Escaped comma escape",
+                "@string/escaped_comma_escape", "a\\,\\");
+        assertTextArray("Escaped escape",
+                "@string/escaped_escape", "\\\\");
+        assertTextArray("Escaped label",
+                "@string/escaped_label", "a\\bc");
+        assertTextArray("Escaped label at beginning",
+                "@string/escaped_label_at_beginning", "\\abc");
+        assertTextArray("Escaped label at end",
+                "@string/escaped_label_at_end", "abc\\");
+        assertTextArray("Escaped label with comma",
+                "@string/escaped_label_with_comma", "a\\,c");
+        assertTextArray("Escaped label with comma at beginning",
+                "@string/escaped_label_with_comma_at_beginning", "\\,bc");
+        assertTextArray("Escaped label with comma at end",
+                "@string/escaped_label_with_comma_at_end", "ab\\,");
+        assertTextArray("Escaped label with successive",
+                "@string/escaped_label_with_successive", "\\,\\\\bc");
+        assertTextArray("Escaped label with escape",
+                "@string/escaped_label_with_escape", "a\\\\c");
+    }
+
+    public void testParseCsvResourceMulti() {
+        assertTextArray("Multiple chars",
+                "@string/multiple_chars", "a", "b", "c");
+        assertTextArray("Multiple chars surrounded by spaces",
+                "@string/multiple_chars_surrounded_by_spaces",
+                " a ", " b ", " c ");
+        assertTextArray("Multiple labels",
+                "@string/multiple_labels", "abc", "def", "ghi");
+        assertTextArray("Multiple labels surrounded by spaces",
+                "@string/multiple_labels_surrounded_by_spaces", " abc ", " def ", " ghi ");
+    }
+
+    public void testParseCsvResourcetMultiEscaped() {
+        assertTextArray("Multiple chars with comma",
+                "@string/multiple_chars_with_comma",
+                "a", "\\,", "c");
+        assertTextArray("Multiple chars with comma surrounded by spaces",
+                "@string/multiple_chars_with_comma_surrounded_by_spaces",
+                " a ", " \\, ", " c ");
+        assertTextArray("Multiple labels with escape",
+                "@string/multiple_labels_with_escape",
+                "\\abc", "d\\ef", "gh\\i");
+        assertTextArray("Multiple labels with escape surrounded by spaces",
+                "@string/multiple_labels_with_escape_surrounded_by_spaces",
+                " \\abc ", " d\\ef ", " gh\\i ");
+        assertTextArray("Multiple labels with comma and escape",
+                "@string/multiple_labels_with_comma_and_escape",
+                "ab\\\\", "d\\\\\\,", "g\\,i");
+        assertTextArray("Multiple labels with comma and escape surrounded by spaces",
+                "@string/multiple_labels_with_comma_and_escape_surrounded_by_spaces",
+                " ab\\\\ ", " d\\\\\\, ", " g\\,i ");
+    }
+
+    public void testParseMultipleResources() {
+        assertTextArray("Literals and resources",
+                "1,@string/multiple_chars,z", "1", "a", "b", "c", "z");
+        assertTextArray("Literals and resources and escape at end",
+                "\\1,@string/multiple_chars,z\\", "\\1", "a", "b", "c", "z\\");
+        assertTextArray("Multiple single resource chars and labels",
+                "@string/single_char,@string/single_label,@string/escaped_comma",
+                "a", "abc", "\\,");
+        assertTextArray("Multiple single resource chars and labels 2",
+                "@string/single_char,@string/single_label,@string/escaped_comma_escape",
+                "a", "abc", "a\\,\\");
+        assertTextArray("Multiple multiple resource chars and labels",
+                "@string/multiple_chars,@string/multiple_labels,@string/multiple_chars_with_comma",
+                "a", "b", "c", "abc", "def", "ghi", "a", "\\,", "c");
+        assertTextArray("Concatenated resources",
+                "@string/multiple_chars@string/multiple_labels@string/multiple_chars_with_comma",
+                "a", "b", "cabc", "def", "ghia", "\\,", "c");
+        assertTextArray("Concatenated resource and literal",
+                "abc@string/multiple_labels",
+                "abcabc", "def", "ghi");
+    }
+
+    public void testParseIndirectReference() {
+        assertTextArray("Indirect",
+                "@string/indirect_string", "a", "b", "c");
+        assertTextArray("Indirect with literal",
+                "1,@string/indirect_string_with_literal,2", "1", "x", "a", "b", "c", "y", "2");
+    }
+
+    public void testParseInfiniteIndirectReference() {
+        assertError("Infinite indirection",
+                "1,@string/infinite_indirection,2", "1", "infinite", "<infinite>", "loop", "2");
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
new file mode 100644
index 0000000..3fc2b02
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -0,0 +1,497 @@
+/*
+ * 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.keyboard.internal;
+
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.R;
+
+import java.util.Arrays;
+
+public class KeySpecParserTests extends AndroidTestCase {
+    private Resources mRes;
+
+    private static final int ICON_UNDEFINED = KeyboardIconsSet.ICON_UNDEFINED;
+
+    private static final String CODE_SETTINGS_RES = "integer/key_settings";
+    private static final String ICON_SETTINGS_NAME = "settingsKey";
+
+    private static final String CODE_SETTINGS = "@" + CODE_SETTINGS_RES;
+    private static final String ICON_SETTINGS = "@icon/" + ICON_SETTINGS_NAME;
+    private static final String CODE_NON_EXISTING = "@integer/non_existing";
+    private static final String ICON_NON_EXISTING = "@icon/non_existing";
+
+    private int mCodeSettings;
+    private int mSettingsIconId;
+
+    @Override
+    protected void setUp() {
+        Resources res = getContext().getResources();
+        mRes = res;
+
+        final String packageName = res.getResourcePackageName(R.string.english_ime_name);
+        final int codeId = res.getIdentifier(CODE_SETTINGS_RES, null, packageName);
+        mCodeSettings = res.getInteger(codeId);
+        mSettingsIconId = KeyboardIconsSet.getIconId(ICON_SETTINGS_NAME);
+    }
+
+    private void assertParser(String message, String moreKeySpec, String expectedLabel,
+            String expectedOutputText, int expectedIcon, int expectedCode) {
+        String actualLabel = KeySpecParser.getLabel(moreKeySpec);
+        assertEquals(message + ": label:", expectedLabel, actualLabel);
+
+        String actualOutputText = KeySpecParser.getOutputText(moreKeySpec);
+        assertEquals(message + ": ouptputText:", expectedOutputText, actualOutputText);
+
+        int actualIcon = KeySpecParser.getIconId(moreKeySpec);
+        assertEquals(message + ": icon:", expectedIcon, actualIcon);
+
+        int actualCode = KeySpecParser.getCode(mRes, moreKeySpec);
+        assertEquals(message + ": codes value:", expectedCode, actualCode);
+    }
+
+    private void assertParserError(String message, String moreKeySpec, String expectedLabel,
+            String expectedOutputText, int expectedIcon, int expectedCode) {
+        try {
+            assertParser(message, moreKeySpec, expectedLabel, expectedOutputText, expectedIcon,
+                    expectedCode);
+            fail(message);
+        } catch (Exception pcpe) {
+            // success.
+        }
+    }
+
+    // \U001d11e: MUSICAL SYMBOL G CLEF
+    private static final String PAIR1 = "\ud834\udd1e";
+    private static final int CODE1 = PAIR1.codePointAt(0);
+    // \U001d122: MUSICAL SYMBOL F CLEF
+    private static final String PAIR2 = "\ud834\udd22";
+    private static final int CODE2 = PAIR2.codePointAt(0);
+    // \U002f8a6: CJK COMPATIBILITY IDEOGRAPH-2F8A6; variant character of \u6148.
+    private static final String PAIR3 = "\ud87e\udca6";
+    private static final String SURROGATE1 = PAIR1 + PAIR2;
+    private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3;
+
+    public void testSingleLetter() {
+        assertParser("Single letter", "a",
+                "a", null, ICON_UNDEFINED, 'a');
+        assertParser("Single surrogate", PAIR1,
+                PAIR1, null, ICON_UNDEFINED, CODE1);
+        assertParser("Single escaped bar", "\\|",
+                "|", null, ICON_UNDEFINED, '|');
+        assertParser("Single escaped escape", "\\\\",
+                "\\", null, ICON_UNDEFINED, '\\');
+        assertParser("Single comma", ",",
+                ",", null, ICON_UNDEFINED, ',');
+        assertParser("Single escaped comma", "\\,",
+                ",", null, ICON_UNDEFINED, ',');
+        assertParser("Single escaped letter", "\\a",
+                "a", null, ICON_UNDEFINED, 'a');
+        assertParser("Single escaped surrogate", "\\" + PAIR2,
+                PAIR2, null, ICON_UNDEFINED, CODE2);
+        assertParser("Single at", "@",
+                "@", null, ICON_UNDEFINED, '@');
+        assertParser("Single escaped at", "\\@",
+                "@", null, ICON_UNDEFINED, '@');
+        assertParser("Single output text letter", "a|a",
+                "a", null, ICON_UNDEFINED, 'a');
+        assertParser("Single surrogate pair outputText", "G Clef|" + PAIR1,
+                "G Clef", null, ICON_UNDEFINED, CODE1);
+        assertParser("Single letter with outputText", "a|abc",
+                "a", "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with surrogate outputText", "a|" + SURROGATE1,
+                "a", SURROGATE1, ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single surrogate with outputText", PAIR3 + "|abc",
+                PAIR3, "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with escaped outputText", "a|a\\|c",
+                "a", "a|c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with escaped surrogate outputText",
+                "a|" + PAIR1 + "\\|" + PAIR2,
+                "a", PAIR1 + "|" + PAIR2, ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with comma outputText", "a|a,b",
+                "a", "a,b", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with escaped comma outputText", "a|a\\,b",
+                "a", "a,b", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with outputText starts with at", "a|@bc",
+                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with surrogate outputText starts with at", "a|@" + SURROGATE2,
+                "a", "@" + SURROGATE2, ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with outputText contains at", "a|a@c",
+                "a", "a@c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single letter with escaped at outputText", "a|\\@bc",
+                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Single escaped escape with single outputText", "\\\\|\\\\",
+                "\\", null, ICON_UNDEFINED, '\\');
+        assertParser("Single escaped bar with single outputText", "\\||\\|",
+                "|", null, ICON_UNDEFINED, '|');
+        assertParser("Single letter with code", "a|" + CODE_SETTINGS,
+                "a", null, ICON_UNDEFINED, mCodeSettings);
+    }
+
+    public void testLabel() {
+        assertParser("Simple label", "abc",
+                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Simple surrogate label", SURROGATE1,
+                SURROGATE1, SURROGATE1, ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped bar", "a\\|c",
+                "a|c", "a|c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Surrogate label with escaped bar", PAIR1 + "\\|" + PAIR2,
+                PAIR1 + "|" + PAIR2, PAIR1 + "|" + PAIR2,
+                ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped escape", "a\\\\c",
+                "a\\c", "a\\c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with comma", "a,c",
+                "a,c", "a,c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped comma", "a\\,c",
+                "a,c", "a,c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label starts with at", "@bc",
+                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Surrogate label starts with at", "@" + SURROGATE1,
+                "@" + SURROGATE1, "@" + SURROGATE1, ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label contains at", "a@c",
+                "a@c", "a@c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped at", "\\@bc",
+                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped letter", "\\abc",
+                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with outputText", "abc|def",
+                "abc", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with comma and outputText", "a,c|def",
+                "a,c", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Escaped comma label with outputText", "a\\,c|def",
+                "a,c", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Escaped label with outputText", "a\\|c|def",
+                "a|c", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped bar outputText", "abc|d\\|f",
+                "abc", "d|f", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Escaped escape label with outputText", "a\\\\|def",
+                "a\\", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label starts with at and outputText", "@bc|def",
+                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label contains at label and outputText", "a@c|def",
+                "a@c", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Escaped at label with outputText", "\\@bc|def",
+                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with comma outputText", "abc|a,b",
+                "abc", "a,b", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped comma outputText", "abc|a\\,b",
+                "abc", "a,b", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with outputText starts with at", "abc|@bc",
+                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with outputText contains at", "abc|a@c",
+                "abc", "a@c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped at outputText", "abc|\\@bc",
+                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with escaped bar outputText", "abc|d\\|f",
+                "abc", "d|f", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Escaped bar label with escaped bar outputText", "a\\|c|d\\|f",
+                "a|c", "d|f", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label with code", "abc|" + CODE_SETTINGS,
+                "abc", null, ICON_UNDEFINED, mCodeSettings);
+        assertParser("Escaped label with code", "a\\|c|" + CODE_SETTINGS,
+                "a|c", null, ICON_UNDEFINED, mCodeSettings);
+    }
+
+    public void testIconAndCode() {
+        assertParser("Icon with outputText", ICON_SETTINGS + "|abc",
+                null, "abc", mSettingsIconId, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Icon with outputText starts with at", ICON_SETTINGS + "|@bc",
+                null, "@bc", mSettingsIconId, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Icon with outputText contains at", ICON_SETTINGS + "|a@c",
+                null, "a@c", mSettingsIconId, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Icon with escaped at outputText", ICON_SETTINGS + "|\\@bc",
+                null, "@bc", mSettingsIconId, Keyboard.CODE_OUTPUT_TEXT);
+        assertParser("Label starts with at and code", "@bc|" + CODE_SETTINGS,
+                "@bc", null, ICON_UNDEFINED, mCodeSettings);
+        assertParser("Label contains at and code", "a@c|" + CODE_SETTINGS,
+                "a@c", null, ICON_UNDEFINED, mCodeSettings);
+        assertParser("Escaped at label with code", "\\@bc|" + CODE_SETTINGS,
+                "@bc", null, ICON_UNDEFINED, mCodeSettings);
+        assertParser("Icon with code", ICON_SETTINGS + "|" + CODE_SETTINGS,
+                null, null, mSettingsIconId, mCodeSettings);
+    }
+
+    public void testFormatError() {
+        assertParserError("Empty spec", "", null,
+                null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Empty label with outputText", "|a",
+                null, "a", ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Empty label with code", "|" + CODE_SETTINGS,
+                null, null, ICON_UNDEFINED, mCodeSettings);
+        assertParserError("Empty outputText with label", "a|",
+                "a", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Empty outputText with icon", ICON_SETTINGS + "|",
+                null, null, mSettingsIconId, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Empty icon and code", "|",
+                null, null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Icon without code", ICON_SETTINGS,
+                null, null, mSettingsIconId, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc",
+                null, "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
+        assertParserError("Non existing code", "abc|" + CODE_NON_EXISTING,
+                "abc", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Third bar at end", "a|b|",
+                "a", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Multiple bar", "a|b|c",
+                "a", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Multiple bar with label and code", "a|" + CODE_SETTINGS + "|c",
+                "a", null, ICON_UNDEFINED, mCodeSettings);
+        assertParserError("Multiple bar with icon and outputText", ICON_SETTINGS + "|b|c",
+                null, null, mSettingsIconId, Keyboard.CODE_UNSPECIFIED);
+        assertParserError("Multiple bar with icon and code",
+                ICON_SETTINGS + "|" + CODE_SETTINGS + "|c",
+                null, null, mSettingsIconId, mCodeSettings);
+    }
+
+    private static void assertMoreKeys(String message, String[] moreKeys,
+            String[] additionalMoreKeys, String[] expected) {
+        final String[] actual = KeySpecParser.insertAddtionalMoreKeys(
+                moreKeys, additionalMoreKeys);
+        if (expected == null && actual == null) {
+            return;
+        }
+        if (expected == null || actual == null) {
+            assertEquals(message, Arrays.toString(expected), Arrays.toString(actual));
+        } else {
+            if (expected.length != actual.length) {
+                assertEquals(message, Arrays.toString(expected), Arrays.toString(actual));
+            }
+            for (int i = 0; i < expected.length; i++) {
+                if (!actual[i].equals(expected[i])) {
+                    assertEquals(message, Arrays.toString(expected), Arrays.toString(actual));
+                }
+            }
+        }
+    }
+
+    public void testEmptyEntry() {
+        assertMoreKeys("null more keys and null additons",
+                null,
+                null,
+                null);
+        assertMoreKeys("null more keys and empty additons",
+                null,
+                new String[0],
+                null);
+        assertMoreKeys("empty more keys and null additons",
+                new String[0],
+                null,
+                null);
+        assertMoreKeys("empty more keys and empty additons",
+                new String[0],
+                new String[0],
+                null);
+
+        assertMoreKeys("filter out empty more keys",
+                new String[] { null, "a", "", "b", null },
+                null,
+                new String[] { "a", "b" });
+        assertMoreKeys("filter out empty additons",
+                new String[] { "a", "%", "b", "%", "c", "%", "d" },
+                new String[] { null, "A", "", "B", null },
+                new String[] { "a", "A", "b", "B", "c", "d" });
+    }
+
+    public void testInsertAdditionalMoreKeys() {
+        // Escaped marker.
+        assertMoreKeys("escaped marker",
+                new String[] { "\\%", "%-)" },
+                new String[] { "1", "2" },
+                new String[] { "1", "2", "\\%", "%-)" });
+
+        // 0 more key.
+        assertMoreKeys("null & null", null, null, null);
+        assertMoreKeys("null & 1 additon",
+                null,
+                new String[] { "1" },
+                new String[] { "1" });
+        assertMoreKeys("null & 2 additons",
+                null,
+                new String[] { "1", "2" },
+                new String[] { "1", "2" });
+
+        // 0 additional more key.
+        assertMoreKeys("1 more key & null",
+                new String[] { "A" },
+                null,
+                new String[] { "A" });
+        assertMoreKeys("2 more keys & null",
+                new String[] { "A", "B" },
+                null,
+                new String[] { "A", "B" });
+
+        // No marker.
+        assertMoreKeys("1 more key & 1 addtional & no marker",
+                new String[] { "A" },
+                new String[] { "1" },
+                new String[] { "1", "A" });
+        assertMoreKeys("1 more key & 2 addtionals & no marker",
+                new String[] { "A" },
+                new String[] { "1", "2" },
+                new String[] { "1", "2", "A" });
+        assertMoreKeys("2 more keys & 1 addtional & no marker",
+                new String[] { "A", "B" },
+                new String[] { "1" },
+                new String[] { "1", "A", "B" });
+        assertMoreKeys("2 more keys & 2 addtionals & no marker",
+                new String[] { "A", "B" },
+                new String[] { "1", "2" },
+                new String[] { "1", "2", "A", "B" });
+
+        // 1 marker.
+        assertMoreKeys("1 more key & 1 additon & marker at head",
+                new String[] { "%", "A" },
+                new String[] { "1" },
+                new String[] { "1", "A" });
+        assertMoreKeys("1 more key & 1 additon & marker at tail",
+                new String[] { "A", "%" },
+                new String[] { "1" },
+                new String[] { "A", "1" });
+        assertMoreKeys("2 more keys & 1 additon & marker at middle",
+                new String[] { "A", "%", "B" },
+                new String[] { "1" },
+                new String[] { "A", "1", "B" });
+
+        // 1 marker & excess additional more keys.
+        assertMoreKeys("1 more key & 2 additons & marker at head",
+                new String[] { "%", "A", "B" },
+                new String[] { "1", "2" },
+                new String[] { "1", "A", "B", "2" });
+        assertMoreKeys("1 more key & 2 additons & marker at tail",
+                new String[] { "A", "B", "%" },
+                new String[] { "1", "2" },
+                new String[] { "A", "B", "1", "2" });
+        assertMoreKeys("2 more keys & 2 additons & marker at middle",
+                new String[] { "A", "%", "B" },
+                new String[] { "1", "2" },
+                new String[] { "A", "1", "B", "2" });
+
+        // 2 markers.
+        assertMoreKeys("0 more key & 2 addtional & 2 markers",
+                new String[] { "%", "%" },
+                new String[] { "1", "2" },
+                new String[] { "1", "2" });
+        assertMoreKeys("1 more key & 2 addtional & 2 markers at head",
+                new String[] { "%", "%", "A" },
+                new String[] { "1", "2" },
+                new String[] { "1", "2", "A" });
+        assertMoreKeys("1 more key & 2 addtional & 2 markers at tail",
+                new String[] { "A", "%", "%" },
+                new String[] { "1", "2" },
+                new String[] { "A", "1", "2" });
+        assertMoreKeys("2 more keys & 2 addtional & 2 markers at middle",
+                new String[] { "A", "%", "%", "B" },
+                new String[] { "1", "2" },
+                new String[] { "A", "1", "2", "B" });
+        assertMoreKeys("2 more keys & 2 addtional & 2 markers at head & middle",
+                new String[] { "%", "A", "%", "B" },
+                new String[] { "1", "2" },
+                new String[] { "1", "A", "2", "B" });
+        assertMoreKeys("2 more keys & 2 addtional & 2 markers at head & tail",
+                new String[] { "%", "A", "B", "%" },
+                new String[] { "1", "2" },
+                new String[] { "1", "A", "B", "2" });
+        assertMoreKeys("2 more keys & 2 addtional & 2 markers at middle & tail",
+                new String[] { "A", "%", "B", "%" },
+                new String[] { "1", "2" },
+                new String[] { "A", "1", "B", "2" });
+
+        // 2 markers & excess additional more keys.
+        assertMoreKeys("0 more key & 2 additons & 2 markers",
+                new String[] { "%", "%" },
+                new String[] { "1", "2", "3" },
+                new String[] { "1", "2", "3" });
+        assertMoreKeys("1 more key & 2 additons & 2 markers at head",
+                new String[] { "%", "%", "A" },
+                new String[] { "1", "2", "3" },
+                new String[] { "1", "2", "A", "3" });
+        assertMoreKeys("1 more key & 2 additons & 2 markers at tail",
+                new String[] { "A", "%", "%" },
+                new String[] { "1", "2", "3" },
+                new String[] { "A", "1", "2", "3" });
+        assertMoreKeys("2 more keys & 2 additons & 2 markers at middle",
+                new String[] { "A", "%", "%", "B" },
+                new String[] { "1", "2", "3" },
+                new String[] { "A", "1", "2", "B", "3" });
+        assertMoreKeys("2 more keys & 2 additons & 2 markers at head & middle",
+                new String[] { "%", "A", "%", "B" },
+                new String[] { "1", "2", "3" },
+                new String[] { "1", "A", "2", "B", "3" });
+        assertMoreKeys("2 more keys & 2 additons & 2 markers at head & tail",
+                new String[] { "%", "A", "B", "%" },
+                new String[] { "1", "2", "3" },
+                new String[] { "1", "A", "B", "2", "3" });
+        assertMoreKeys("2 more keys & 2 additons & 2 markers at middle & tail",
+                new String[] { "A", "%", "B", "%" },
+                new String[] { "1", "2", "3" },
+                new String[] { "A", "1", "B", "2", "3" });
+
+        // 0 addtional more key and excess markers.
+        assertMoreKeys("0 more key & null & excess marker",
+                new String[] { "%" },
+                null,
+                null);
+        assertMoreKeys("1 more key & null & excess marker at head",
+                new String[] { "%", "A" },
+                null,
+                new String[] { "A" });
+        assertMoreKeys("1 more key & null & excess marker at tail",
+                new String[] { "A", "%" },
+                null,
+                new String[] { "A" });
+        assertMoreKeys("2 more keys & null & excess marker at middle",
+                new String[] { "A", "%", "B" },
+                null,
+                new String[] { "A", "B" });
+        assertMoreKeys("2 more keys & null & excess markers",
+                new String[] { "%", "A", "%", "B", "%" },
+                null,
+                new String[] { "A", "B" });
+
+        // Excess markers.
+        assertMoreKeys("0 more key & 1 additon & excess marker",
+                new String[] { "%", "%" },
+                new String[] { "1" },
+                new String[] { "1" });
+        assertMoreKeys("1 more key & 1 additon & excess marker at head",
+                new String[] { "%", "%", "A" },
+                new String[] { "1" },
+                new String[] { "1", "A" });
+        assertMoreKeys("1 more key & 1 additon & excess marker at tail",
+                new String[] { "A", "%", "%" },
+                new String[] { "1" },
+                new String[] { "A", "1" });
+        assertMoreKeys("2 more keys & 1 additon & excess marker at middle",
+                new String[] { "A", "%", "%", "B" },
+                new String[] { "1" },
+                new String[] { "A", "1", "B" });
+        assertMoreKeys("2 more keys & 1 additon & excess markers",
+                new String[] { "%", "A", "%", "B", "%" },
+                new String[] { "1" },
+                new String[] { "1", "A", "B" });
+        assertMoreKeys("2 more keys & 2 additons & excess markers",
+                new String[] { "%", "A", "%", "B", "%" },
+                new String[] { "1", "2" },
+                new String[] { "1", "A", "2", "B" });
+        assertMoreKeys("2 more keys & 3 additons & excess markers",
+                new String[] { "%", "A", "%", "%", "B", "%" },
+                new String[] { "1", "2", "3" },
+                new String[] { "1", "A", "2", "3", "B" });
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java
deleted file mode 100644
index 4050a71..0000000
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.keyboard.internal;
-
-import com.android.inputmethod.keyboard.internal.KeyStyles.EmptyKeyStyle;
-
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-
-public class KeyStylesTests extends AndroidTestCase {
-    private static String format(String message, Object expected, Object actual) {
-        return message + " expected:<" + expected + "> but was:<" + actual + ">";
-    }
-
-    private static void assertTextArray(String message, CharSequence value,
-            CharSequence ... expected) {
-        final CharSequence actual[] = EmptyKeyStyle.parseCsvText(value);
-        if (expected.length == 0) {
-            assertNull(message, actual);
-            return;
-        }
-        assertSame(message + ": result length", expected.length, actual.length);
-        for (int i = 0; i < actual.length; i++) {
-            final boolean equals = TextUtils.equals(expected[i], actual[i]);
-            assertTrue(format(message + ": result at " + i + ":", expected[i], actual[i]), equals);
-        }
-    }
-
-    public void testParseCsvTextZero() {
-        assertTextArray("Empty string", "");
-    }
-
-    public void testParseCsvTextSingle() {
-        assertTextArray("Single char", "a", "a");
-        assertTextArray("Space", " ", " ");
-        assertTextArray("Single label", "abc", "abc");
-        assertTextArray("Spaces", "   ", "   ");
-        assertTextArray("Spaces in label", "a b c", "a b c");
-        assertTextArray("Spaces at beginning of label", " abc", " abc");
-        assertTextArray("Spaces at end of label", "abc ", "abc ");
-        assertTextArray("label surrounded by spaces", " abc ", " abc ");
-    }
-
-    public void testParseCsvTextSingleEscaped() {
-        assertTextArray("Escaped char", "\\a", "a");
-        assertTextArray("Escaped comma", "\\,", ",");
-        assertTextArray("Escaped escape", "\\\\", "\\");
-        assertTextArray("Escaped label", "a\\bc", "abc");
-        assertTextArray("Escaped label at begininng", "\\abc", "abc");
-        assertTextArray("Escaped label with comma", "a\\,c", "a,c");
-        assertTextArray("Escaped label with comma at beginning", "\\,bc", ",bc");
-        assertTextArray("Escaped label with successive", "\\,\\\\bc", ",\\bc");
-        assertTextArray("Escaped label with escape", "a\\\\c", "a\\c");
-    }
-
-    public void testParseCsvTextMulti() {
-        assertTextArray("Multiple chars", "a,b,c", "a", "b", "c");
-        assertTextArray("Multiple chars surrounded by spaces", " a , b , c ", " a ", " b ", " c ");
-        assertTextArray("Multiple labels", "abc,def,ghi", "abc", "def", "ghi");
-        assertTextArray("Multiple labels surrounded by spaces", " abc , def , ghi ",
-                " abc ", " def ", " ghi ");
-    }
-
-    public void testParseCsvTextMultiEscaped() {
-        assertTextArray("Multiple chars with comma", "a,\\,,c", "a", ",", "c");
-        assertTextArray("Multiple chars with comma surrounded by spaces", " a , \\, , c ",
-                " a ", " , ", " c ");
-        assertTextArray("Multiple labels with escape", "\\abc,d\\ef,gh\\i", "abc", "def", "ghi");
-        assertTextArray("Multiple labels with escape surrounded by spaces",
-                " \\abc , d\\ef , gh\\i ", " abc ", " def ", " ghi ");
-        assertTextArray("Multiple labels with comma and escape",
-                "ab\\\\,d\\\\\\,,g\\,i", "ab\\", "d\\,", "g,i");
-        assertTextArray("Multiple labels with comma and escape surrounded by spaces",
-                " ab\\\\ , d\\\\\\, , g\\,i ", " ab\\ ", " d\\, ", " g,i ");
-    }
-}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
new file mode 100644
index 0000000..0e6caaf
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+public class KeyboardStateMultiTouchTests extends KeyboardStateTestsBase {
+    // Chording input in alphabet.
+    public void testChordingAlphabet() {
+        // Press shift key and hold, enter into choring shift state.
+        pressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+        // Press/release letter key.
+        chordingPressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Release shift key, switch back to alphabet.
+        releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
+
+        // Press "?123" key and hold, enter into choring symbols state.
+        pressKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+        // Press/release symbol letter key.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Release "ABC" key, switch back to alphabet.
+        releaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+    }
+
+    // Chording input in shift locked.
+    public void testChordingShiftLocked() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press shift key and hold, enter into choring shift state.
+        pressKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED);
+        // Press/release letter key.
+        chordingPressAndReleaseKey('Z', ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
+        // Release shift key, switch back to alphabet shift locked.
+        releaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED);
+
+        // Press "?123" key and hold, enter into choring symbols state.
+        pressKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+        // Press/release symbol letter key.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Release "123?" key, switch back to alphabet shift locked.
+        releaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Chording input in symbols.
+    public void testChordingSymbols() {
+        // Press/release "?123" key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press "=\<" key and hold, enter into choring symbols shifted state.
+        pressKey(CODE_SHIFT, SYMBOLS_SHIFTED);
+        // Press/release symbol letter key.
+        chordingPressAndReleaseKey('1', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Release "=\<" key, switch back to symbols.
+        releaseKey(CODE_SHIFT, SYMBOLS_UNSHIFTED);
+
+        // Press "ABC" key and hold, enter into choring alphabet state.
+        pressKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+        // Press/release letter key.
+        chordingPressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Release "ABC" key, switch back to symbols.
+        releaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+
+        // Alphabet shifted -> symbols -> "ABC" key + letter -> symbols
+        // -> alphabet.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press "ABC" key, enter into chording alphabet state.
+        pressKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+        // Enter/release letter key.
+        chordingPressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Release "ABC" key, switch back to symbols.
+        releaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet (not alphabet shifted).
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shift locked -> symbols -> "ABC" key + letter -> symbols ->
+        // alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press "ABC" key, enter into chording alphabet shift locked.
+        pressKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED);
+        // Enter/release letter key.
+        chordingPressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Release "ABC" key, switch back to symbols.
+        releaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols -> "=\<" key + letter -> symbols ->
+        // alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press "=\<" key, enter into symbols shifted chording state.
+        pressKey(CODE_SHIFT, SYMBOLS_SHIFTED);
+        // Enter/release symbols shift letter key.
+        chordingPressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Release "=\<" key, switch back to symbols.
+        releaseKey(CODE_SHIFT, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Chording input in symbol shifted.
+    public void testChordingSymbolsShifted() {
+        // Press/release "?123" key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+
+        // Press "?123" key and hold, enter into chording symbols state.
+        pressKey(CODE_SHIFT, SYMBOLS_UNSHIFTED);
+        // Press/release symbol letter key.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Release "=\<" key, switch back to symbols shifted state.
+        releaseKey(CODE_SHIFT, SYMBOLS_SHIFTED);
+
+        // Press "ABC" key and hold, enter into choring alphabet state.
+        pressKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+        // Press/release letter key.
+        chordingPressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Release "ABC" key, switch back to symbols.
+        releaseKey(CODE_SYMBOL, SYMBOLS_SHIFTED);
+
+        // Alphabet shifted -> symbols shifted -> "ABC" key + letter -> symbols shifted ->
+        // alphabet.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press "ABC" key, enter into chording alphabet state.
+        pressKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+        // Enter/release letter key.
+        chordingPressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Release "ABC" key, switch back to symbols shifted.
+        releaseKey(CODE_SYMBOL, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet (not alphabet shifted).
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shift locked -> symbols shifted -> "ABC" key + letter -> symbols shifted
+        // -> alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press "ABC" key, enter into chording alphabet shift locked.
+        pressKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED);
+        // Enter/release letter key.
+        chordingPressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Release "ABC" key, switch back to symbols shifted.
+        releaseKey(CODE_SYMBOL, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols shifted -> "=\<" key + letter -> symbols shifted
+        // -> alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press "=\<" key, enter into symbols chording state.
+        pressKey(CODE_SHIFT, SYMBOLS_UNSHIFTED);
+        // Enter/release symbols letter key.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Release "=\<" key, switch back to symbols shifted.
+        releaseKey(CODE_SHIFT, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Chording input in automatic upper case.
+    public void testChordingAutomaticUpperCase() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state with auto caps enabled.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+        // Press shift key and hold, enter into chording shift state.
+        pressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+        // Press/release letter key.
+        chordingPressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Release shift key, switch back to alphabet.
+        releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
+
+        // Update shift state with auto caps enabled.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+        // Press "123?" key and hold, enter into chording symbols state.
+        pressKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+        // Press/release symbol letter key.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Release "123?" key, switch back to alphabet.
+        releaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+    }
+
+    // Chording letter key with shift key.
+    public void testChordingLetterAndShiftKey() {
+        // Press letter key and hold.
+        pressKey('z', ALPHABET_UNSHIFTED);
+        // Press shift key, {@link PointerTracker} will fire a phantom release letter key.
+        chordingReleaseKey('z', ALPHABET_UNSHIFTED);
+        chordingPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+        // Press another letter key and hold.
+        chordingPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Release shift key
+        releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
new file mode 100644
index 0000000..de2a50f
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+public class KeyboardStateSingleTouchTests extends KeyboardStateTestsBase {
+    // Shift key in alphabet.
+    public void testShiftAlphabet() {
+        // Press/release shift key, enter into alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter into alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release letter key, switch back to alphabet.
+        pressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Shift key in symbols.
+    public void testShiftSymbols() {
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+
+        // Press/release "?123" key, back to symbols.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release symbol letter key, remain in symbols shifted.
+        pressAndReleaseKey('1', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+    }
+
+    // Switching between alphabet and symbols.
+    public void testAlphabetAndSymbols() {
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, back to alphabet.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, back to alphabet.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release "?123" key, back to symbols (not symbols shifted).
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+    }
+
+    // Switching between alphabet shifted and symbols.
+    public void testAlphabetShiftedAndSymbols() {
+        // Press/release shift key, enter into alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, back to alphabet (not alphabet shifted).
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter into alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\< key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, back to alphabet (not alphabet shifted).
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Switching between alphabet shift locked and symbols.
+    public void testAlphabetShiftLockedAndSymbols() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, back to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, back to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, back to symbols (not symbols shifted).
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+    }
+
+    // Automatic switch back to alphabet by space key.
+    public void testSwitchBackBySpace() {
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter space, switch back to alphabet.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter symbol shift letter.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter space, switch back to alphabet.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release "?123" key, enter into symbols (not symbols shifted).
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+    }
+
+    // Automatic switch back to alphabet shift locked test by space key.
+    public void testSwitchBackBySpaceShiftLocked() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter space, switch back to alphabet shift locked.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_UNSHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter symbol shift letter.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter space, switch back to alphabet shift locked.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Automatic switch back to alphabet by registered letters.
+    public void testSwitchBackChar() {
+        // Set switch back chars.
+        final String switchBackSymbols = "'";
+        final int switchBackCode = switchBackSymbols.codePointAt(0);
+        setLayoutSwitchBackSymbols(switchBackSymbols);
+        loadKeyboard(ALPHABET_UNSHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter switch back letter, switch back to alphabet.
+        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter symbol shift letter.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter switch abck letter, switch back to alphabet.
+        pressAndReleaseKey(switchBackCode, SYMBOLS_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Automatic switch back to alphabet shift locked by registered letters.
+    public void testSwitchBackCharShiftLocked() {
+        // Set switch back chars.
+        final String switchBackSymbols = "'";
+        final int switchBackCode = switchBackSymbols.codePointAt(0);
+        setLayoutSwitchBackSymbols(switchBackSymbols);
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter switch back letter, switch back to alphabet shift locked.
+        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter symbol shift letter.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter switch back letter, switch back to alphabet shift locked.
+        pressAndReleaseKey(switchBackCode, SYMBOLS_SHIFTED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Automatic upper case test
+    public void testAutomaticUpperCase() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Load keyboard, should be in automatic shifted.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release letter key, switch to alphabet.
+        pressAndReleaseKey('A', ALPHABET_AUTOMATIC_SHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release auto caps trigger letter, should be in automatic shifted.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release letter key, remain in alphabet.
+        pressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release auto caps trigger letter, should be in automatic shifted.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release symbol letter key, remain in symbols.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release space, switch back to automatic shifted.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release symbol shift letter key, remain in symbols shifted.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release space, switch back to automatic shifted.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+    }
+
+    // Long press shift key.
+    public void testLongPressShift() {
+        // Set auto caps mode off.
+        setAutoCapsMode(NO_AUTO_CAPS);
+        // Load keyboard, should be in alphabet.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release letter key, remain in shift locked.
+        pressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Press/release word separator, remain in shift locked.
+        pressAndReleaseKey(CODE_SPACE, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Long press shift key, back to alphabet.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Load keyboard, should be in automatic shifted.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Double tap shift key.
+    public void testDoubleTapShift() {
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter alphabet manual shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Load keyboard, should be in automatic shifted.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Update shift state.
+    public void testUpdateShiftState() {
+        // Set auto caps mode off.
+        setAutoCapsMode(NO_AUTO_CAPS);
+        // Load keyboard, should be in alphabet.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Update shift state, remained in alphabet.
+        updateShiftState(ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Update shift state, back to alphabet.
+        updateShiftState(ALPHABET_UNSHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Update shift state, remained in alphabet shift locked.
+        updateShiftState(ALPHABET_SHIFT_LOCKED);
+        // Long press shift key, back to alphabet.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Update shift state, remained in symbols.
+        updateShiftState(SYMBOLS_UNSHIFTED);
+
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Update shift state, remained in symbols shifted.
+        updateShiftState(SYMBOLS_SHIFTED);
+
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Load keyboard, should be in automatic shifted.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+        // Update shift state, remained in automatic shifted.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release shift key, enter alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Update shift state, enter to automatic shifted (not alphabet shifted).
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Update shift state, remained in alphabet shift locked (not automatic shifted).
+        updateShiftState(ALPHABET_SHIFT_LOCKED);
+        // Long press shift key, back to alphabet.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Load keyboard, should be in automatic shifted.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Update shift state, remained in symbols.
+        updateShiftState(SYMBOLS_UNSHIFTED);
+
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Update shift state, remained in symbols shifted.
+        updateShiftState(SYMBOLS_SHIFTED);
+    }
+
+    // Sliding input in alphabet.
+    public void testSlidingAlphabet() {
+        // Alphabet -> shift key + letter -> alphabet.
+        // Press and slide from shift key, enter alphabet shifted.
+        pressAndSlideFromKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Enter/release letter key, switch back to alphabet.
+        pressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet -> "?123" key + letter -> alphabet.
+        // Press and slide from "123?" key, enter symbols.
+        pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter/release into symbol letter key, switch back to alphabet.
+        pressAndReleaseKey('!', SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shifted -> shift key + letter -> alphabet.
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press and slide from shift key, remain alphabet shifted.
+        pressAndSlideFromKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Enter/release letter key, switch back to alphabet (not alphabet shifted).
+        pressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shifted -> "?123" key + letter -> alphabet.
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press and slide from "123?" key, enter symbols.
+        pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter/release into symbol letter key, switch back to alphabet (not alphabet shifted).
+        pressAndReleaseKey('!', SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shift locked -> shift key + letter -> alphabet shift locked.
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press and slide from "123?" key, enter symbols.
+        pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter/release into symbol letter key, switch back to alphabet shift locked.
+        pressAndReleaseKey('!', SYMBOLS_UNSHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> "?123" key + letter -> alphabet shift locked.
+        // Press and slide from shift key, enter alphabet shifted.
+        pressAndSlideFromKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Enter/release letter key, switch back to shift locked.
+        pressAndReleaseKey('Z', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Sliding input in symbols.
+    public void testSlidingSymbols() {
+        // Symbols -> "=\<" key + letter -> symbols.
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press and slide from shift key, enter symols shifted.
+        pressAndSlideFromKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter/release symbol shifted letter key, switch back to symbols.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Symbols -> "ABC" key + letter -> Symbols.
+        // Press and slide from "ABC" key, enter alphabet.
+        pressAndSlideFromKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Enter/release letter key, switch back to symbols.
+        pressAndReleaseKey('a', ALPHABET_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shifted -> symbols -> "ABC" key + letter -> symbols ->
+        // alphabet.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press and slide from "ABC" key.
+        pressAndSlideFromKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Enter/release letter key, switch back to symbols.
+        pressAndReleaseKey('a', ALPHABET_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet (not alphabet shifted).
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shift locked -> symbols -> "ABC" key + letter -> symbols ->
+        // alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press and slide from "ABC" key, enter alphabet shift locked.
+        pressAndSlideFromKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Enter/release letter key, switch back to symbols.
+        pressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols -> "=\<" key + letter -> symbols ->
+        // alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press and slide from "=\<" key, enter symbols shifted.
+        pressAndSlideFromKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Enter/release symbols shift letter key, switch back to symbols.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Sliding input in symbols shifted.
+    public void testSlidingSymbolsShifted() {
+        // Symbols shifted -> "?123" + letter -> symbols shifted.
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press and slide from shift key, enter symbols.
+        pressAndSlideFromKey(CODE_SHIFT, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter/release symbol letter key, switch back to symbols shifted.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_SHIFTED);
+
+        // Symbols shifted -> "ABC" key + letter -> symbols shifted.
+        // Press and slide from "ABC" key, enter alphabet.
+        pressAndSlideFromKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Enter/release letter key, switch back to symbols shifted.
+        pressAndReleaseKey('a', ALPHABET_UNSHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shifted -> symbols shifted -> "ABC" + letter -> symbols shifted ->
+        // alphabet.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press and slide from "ABC" key.
+        pressAndSlideFromKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Enter/release letter key, switch back to symbols shifted.
+        pressAndReleaseKey('a', ALPHABET_UNSHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet (not alphabet shifted).
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Alphabet shift locked -> symbols shifted -> "ABC" + letter -> symbols shifted ->
+        // alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press and slide from "ABC" key.
+        pressAndSlideFromKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Enter/release letter key, switch back to symbols shifted.
+        pressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols shifted -> "?123" + letter -> symbols shifted ->
+        // alphabet shift locked.
+        // Load keyboard
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter into symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press and slide from "?123" key.
+        pressAndSlideFromKey(CODE_SHIFT, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Enter/release symbol letter key, switch back to symbols shifted.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, switch to alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Change focus to new text field.
+    public void testChangeFocus() {
+        // Press/release shift key.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+
+        // Press/release "?123" key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+
+        // Press/release "?123" key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+    }
+
+    // Change focus to auto caps text field.
+    public void testChangeFocusAutoCaps() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release shift key, enter alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release "?123" key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release "?123" key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+    }
+
+    // Change orientation.
+    public void testChangeOrientation() {
+        // Alphabet -> rotate -> alphabet.
+        updateShiftState(ALPHABET_UNSHIFTED);
+        // Rotate device, remain in alphabet.
+        rotateDevice(ALPHABET_UNSHIFTED);
+
+        // Alphabet shifted -> rotate -> alphabet shifted.
+        // Press/release shift key, enter alphabet shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rotate device, remain in alphabet shifted.
+        rotateDevice(ALPHABET_MANUAL_SHIFTED);
+
+        // Alphabet shift locked -> rotate -> alphabet shift locked.
+        // Long press shift key, enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Rotate device, remain in alphabet shift locked.
+        rotateDevice(ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols -> rotate -> symbols ->
+        // Alphabet shift locked.
+        // Press/release "?123" key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Rotate device, remain in symbols,
+        rotateDevice(SYMBOLS_UNSHIFTED);
+        // Press/release "ABC" key, alphabet shift locked state should be maintained.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols shifted -> rotate -> symbols shifted ->
+        // Alphabet shift locked.
+        // Press/release "?123" key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Rotate device, remain in symbols shifted.
+        rotateDevice(SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, alphabet shift locked state should be maintained.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Alphabet shift locked -> symbols shifted -> alphabet shift locked -> rotate ->
+        // Alphabet shift locked -> symbols.
+        // Press/release "?123" key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, enter alphabet shift locked.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+        // Rotate device, remain in alphabet shift locked.
+        rotateDevice(ALPHABET_SHIFT_LOCKED);
+        // Press/release "?123" key, enter symbols (not symbols shifted).
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Alphabet -> symbols shifted -> alphabet -> rotate ->
+        // Alphabet -> symbols.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+        // Press/release "?123" key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "=\<" key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Press/release "ABC" key, enter alphabet.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rotate device, remain in alphabet shift locked.
+        rotateDevice(ALPHABET_UNSHIFTED);
+        // Press/release "?123" key, enter symbols (not symbols shifted).
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+    }
+
+    // Rapidly type shift key.
+    public void testRapidShiftTyping() {
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key to enter alphabet manual shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('j', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Long press shift key to enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('j', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Press/release auto caps trigger letter to enter alphabet automatic shifted.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('j', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
new file mode 100644
index 0000000..96a5466
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.test.AndroidTestCase;
+
+public class KeyboardStateTestsBase extends AndroidTestCase
+        implements MockKeyboardSwitcher.Constants {
+    protected MockKeyboardSwitcher mSwitcher;
+
+    private String mLayoutSwitchBackSymbols = "";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mSwitcher = new MockKeyboardSwitcher();
+        mSwitcher.setAutoCapsMode(NO_AUTO_CAPS);
+
+        loadKeyboard(ALPHABET_UNSHIFTED);
+    }
+
+    public void setAutoCapsMode(boolean autoCaps) {
+        mSwitcher.setAutoCapsMode(autoCaps);
+    }
+
+    public void setLayoutSwitchBackSymbols(String switchBackSymbols) {
+        mLayoutSwitchBackSymbols = switchBackSymbols;
+    }
+
+    private static void assertLayout(int expected, int actual) {
+        assertTrue("expected=" + MockKeyboardSwitcher.getLayoutName(expected)
+                + " actual=" + MockKeyboardSwitcher.getLayoutName(actual),
+                expected == actual);
+    }
+
+    public void updateShiftState(int afterUpdate) {
+        mSwitcher.updateShiftState();
+        assertLayout(afterUpdate, mSwitcher.getLayoutId());
+    }
+
+    public void loadKeyboard(int afterLoad) {
+        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+        updateShiftState(afterLoad);
+    }
+
+    public void rotateDevice(int afterRotate) {
+        mSwitcher.saveKeyboardState();
+        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+        assertLayout(afterRotate, mSwitcher.getLayoutId());
+    }
+
+    private void pressKeyWithoutTimerExpire(int code, int afterPress) {
+        mSwitcher.onPressKey(code);
+        assertLayout(afterPress, mSwitcher.getLayoutId());
+    }
+
+    public void pressKey(int code, int afterPress) {
+        mSwitcher.expireDoubleTapTimeout();
+        pressKeyWithoutTimerExpire(code, afterPress);
+    }
+
+    public void releaseKey(int code, int afterRelease) {
+        mSwitcher.onCodeInput(code, SINGLE);
+        mSwitcher.onReleaseKey(code, NOT_SLIDING);
+        assertLayout(afterRelease, mSwitcher.getLayoutId());
+    }
+
+    public void pressAndReleaseKey(int code, int afterPress, int afterRelease) {
+        pressKey(code, afterPress);
+        releaseKey(code, afterRelease);
+    }
+
+    public void chordingPressKey(int code, int afterPress) {
+        pressKey(code, afterPress);
+    }
+
+    public void chordingReleaseKey(int code, int afterRelease) {
+        mSwitcher.onCodeInput(code, MULTI);
+        mSwitcher.onReleaseKey(code, NOT_SLIDING);
+        assertLayout(afterRelease, mSwitcher.getLayoutId());
+    }
+
+    public void chordingPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+        chordingPressKey(code, afterPress);
+        chordingReleaseKey(code, afterRelease);
+    }
+
+    public void pressAndSlideFromKey(int code, int afterPress, int afterSlide) {
+        pressKey(code, afterPress);
+        mSwitcher.onReleaseKey(code, SLIDING);
+        assertLayout(afterSlide, mSwitcher.getLayoutId());
+    }
+
+    public void longPressAndReleaseKey(int code, int afterPress, int afterLongPress) {
+        pressKey(code, afterPress);
+        mSwitcher.onLongPressTimeout(code);
+        assertLayout(afterLongPress, mSwitcher.getLayoutId());
+        releaseKey(code, afterLongPress);
+    }
+
+    public void secondPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+        pressKeyWithoutTimerExpire(code, afterPress);
+        releaseKey(code, afterRelease);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
new file mode 100644
index 0000000..999e08a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class MockKeyboardSwitcher implements KeyboardState.SwitchActions {
+    public interface Constants {
+        // Argument for {@link KeyboardState#onPressKey} and {@link KeyboardState#onReleaseKey}.
+        public static final boolean NOT_SLIDING = false;
+        public static final boolean SLIDING = true;
+        // Argument for {@link KeyboardState#onCodeInput}.
+        public static final boolean SINGLE = true;
+        public static final boolean MULTI = false;
+        public static final boolean NO_AUTO_CAPS = false;
+        public static final boolean AUTO_CAPS = true;
+
+        public static final int CODE_SHIFT = Keyboard.CODE_SHIFT;
+        public static final int CODE_SYMBOL = Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
+        public static final int CODE_SPACE = Keyboard.CODE_SPACE;
+        public static final int CODE_AUTO_CAPS_TRIGGER = Keyboard.CODE_SPACE;
+
+        public static final int ALPHABET_UNSHIFTED = 0;
+        public static final int ALPHABET_MANUAL_SHIFTED = 1;
+        public static final int ALPHABET_AUTOMATIC_SHIFTED = 2;
+        public static final int ALPHABET_SHIFT_LOCKED = 3;
+        public static final int ALPHABET_SHIFT_LOCK_SHIFTED = 4;
+        public static final int SYMBOLS_UNSHIFTED = 5;
+        public static final int SYMBOLS_SHIFTED = 6;
+    }
+
+    private int mLayout = Constants.ALPHABET_UNSHIFTED;
+
+    private boolean mAutoCapsMode = Constants.NO_AUTO_CAPS;
+    // Following InputConnection's behavior. Simulating InputType.TYPE_TEXT_FLAG_CAP_WORDS.
+    private boolean mAutoCapsState = true;
+
+    private boolean mIsInDoubleTapTimeout;
+    private int mLongPressTimeoutCode;
+
+    private final KeyboardState mState = new KeyboardState(this);
+
+    public int getLayoutId() {
+        return mLayout;
+    }
+
+    public static String getLayoutName(int layoutId) {
+        switch (layoutId) {
+        case Constants.ALPHABET_UNSHIFTED: return "ALPHABET_UNSHIFTED";
+        case Constants.ALPHABET_MANUAL_SHIFTED: return "ALPHABET_MANUAL_SHIFTED";
+        case Constants.ALPHABET_AUTOMATIC_SHIFTED: return "ALPHABET_AUTOMATIC_SHIFTED";
+        case Constants.ALPHABET_SHIFT_LOCKED: return "ALPHABET_SHIFT_LOCKED";
+        case Constants.ALPHABET_SHIFT_LOCK_SHIFTED: return "ALPHABET_SHIFT_LOCK_SHIFTED";
+        case Constants.SYMBOLS_UNSHIFTED: return "SYMBOLS_UNSHIFTED";
+        case Constants.SYMBOLS_SHIFTED: return "SYMBOLS_SHIFTED";
+        default: return "UNKNOWN<" + layoutId + ">";
+        }
+    }
+
+    public void setAutoCapsMode(boolean autoCaps) {
+        mAutoCapsMode = autoCaps;
+    }
+
+    public void expireDoubleTapTimeout() {
+        mIsInDoubleTapTimeout = false;
+    }
+
+    @Override
+    public void setAlphabetKeyboard() {
+        mLayout = Constants.ALPHABET_UNSHIFTED;
+    }
+
+    @Override
+    public void setAlphabetManualShiftedKeyboard() {
+        mLayout = Constants.ALPHABET_MANUAL_SHIFTED;
+    }
+
+    @Override
+    public void setAlphabetAutomaticShiftedKeyboard() {
+        mLayout = Constants.ALPHABET_AUTOMATIC_SHIFTED;
+    }
+
+    @Override
+    public void setAlphabetShiftLockedKeyboard() {
+        mLayout = Constants.ALPHABET_SHIFT_LOCKED;
+    }
+
+    @Override
+    public void setAlphabetShiftLockShiftedKeyboard() {
+        mLayout = Constants.ALPHABET_SHIFT_LOCK_SHIFTED;
+    }
+
+    @Override
+    public void setSymbolsKeyboard() {
+        mLayout = Constants.SYMBOLS_UNSHIFTED;
+    }
+
+    @Override
+    public void setSymbolsShiftedKeyboard() {
+        mLayout = Constants.SYMBOLS_SHIFTED;
+    }
+
+    @Override
+    public void requestUpdatingShiftState() {
+        mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
+    }
+
+    @Override
+    public void startDoubleTapTimer() {
+        mIsInDoubleTapTimeout = true;
+    }
+
+    @Override
+    public void cancelDoubleTapTimer() {
+        mIsInDoubleTapTimeout = false;
+    }
+
+    @Override
+    public boolean isInDoubleTapTimeout() {
+        return mIsInDoubleTapTimeout;
+    }
+
+    @Override
+    public void startLongPressTimer(int code) {
+        mLongPressTimeoutCode = code;
+    }
+
+    @Override
+    public void cancelLongPressTimer() {
+        mLongPressTimeoutCode = 0;
+    }
+
+    @Override
+    public void hapticAndAudioFeedback(int code) {
+        // Nothing to do.
+    }
+
+    public void onLongPressTimeout(int code) {
+        // TODO: Handle simultaneous long presses.
+        if (mLongPressTimeoutCode == code) {
+            mLongPressTimeoutCode = 0;
+            mState.onLongPressTimeout(code);
+        }
+    }
+
+    public void updateShiftState() {
+        mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
+    }
+
+    public void loadKeyboard(String layoutSwitchBackSymbols) {
+        mState.onLoadKeyboard(layoutSwitchBackSymbols);
+    }
+
+    public void saveKeyboardState() {
+        mState.onSaveKeyboardState();
+    }
+
+    public void onPressKey(int code) {
+        mState.onPressKey(code);
+    }
+
+    public void onReleaseKey(int code, boolean withSliding) {
+        mState.onReleaseKey(code, withSliding);
+        if (mLongPressTimeoutCode == code) {
+            mLongPressTimeoutCode = 0;
+        }
+    }
+
+    public void onCodeInput(int code, boolean isSinglePointer) {
+        if (Keyboard.isLetterCode(code)) {
+            mAutoCapsState = (code == Constants.CODE_AUTO_CAPS_TRIGGER);
+        }
+        mState.onCodeInput(code, isSinglePointer, mAutoCapsMode && mAutoCapsState);
+    }
+
+    public void onCancelInput(boolean isSinglePointer) {
+        mState.onCancelInput(isSinglePointer);
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParserTests.java
deleted file mode 100644
index 798fca0..0000000
--- a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParserTests.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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.keyboard.internal;
-
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.R;
-
-public class MoreKeySpecParserTests extends AndroidTestCase {
-    private Resources mRes;
-
-    private static final int ICON_SETTINGS_KEY = 5;
-    private static final int ICON_UNDEFINED = KeyboardIconsSet.ICON_UNDEFINED;
-
-    private static final String CODE_SETTINGS = "@integer/key_settings";
-    private static final String ICON_SETTINGS = "@icon/" + ICON_SETTINGS_KEY;
-    private static final String CODE_NON_EXISTING = "@integer/non_existing";
-    private static final String ICON_NON_EXISTING = "@icon/non_existing";
-
-    private int mCodeSettings;
-
-    @Override
-    protected void setUp() {
-        Resources res = getContext().getResources();
-        mRes = res;
-
-        final String packageName = res.getResourcePackageName(R.string.english_ime_name);
-        final int codeId = res.getIdentifier(CODE_SETTINGS.substring(1), null, packageName);
-        mCodeSettings = res.getInteger(codeId);
-    }
-
-    private void assertParser(String message, String moreKeySpec, String expectedLabel,
-            String expectedOutputText, int expectedIcon, int expectedCode) {
-        String actualLabel = MoreKeySpecParser.getLabel(moreKeySpec);
-        assertEquals(message + ": label:", expectedLabel, actualLabel);
-
-        String actualOutputText = MoreKeySpecParser.getOutputText(moreKeySpec);
-        assertEquals(message + ": ouptputText:", expectedOutputText, actualOutputText);
-
-        int actualIcon = MoreKeySpecParser.getIconId(moreKeySpec);
-        assertEquals(message + ": icon:", expectedIcon, actualIcon);
-
-        int actualCode = MoreKeySpecParser.getCode(mRes, moreKeySpec);
-        assertEquals(message + ": codes value:", expectedCode, actualCode);
-    }
-
-    private void assertParserError(String message, String moreKeySpec, String expectedLabel,
-            String expectedOutputText, int expectedIcon, int expectedCode) {
-        try {
-            assertParser(message, moreKeySpec, expectedLabel, expectedOutputText, expectedIcon,
-                    expectedCode);
-            fail(message);
-        } catch (MoreKeySpecParser.MoreKeySpecParserError pcpe) {
-            // success.
-        }
-    }
-
-    public void testSingleLetter() {
-        assertParser("Single letter", "a",
-                "a", null, ICON_UNDEFINED, 'a');
-        assertParser("Single escaped bar", "\\|",
-                "|", null, ICON_UNDEFINED, '|');
-        assertParser("Single escaped escape", "\\\\",
-                "\\", null, ICON_UNDEFINED, '\\');
-        assertParser("Single comma", ",",
-                ",", null, ICON_UNDEFINED, ',');
-        assertParser("Single escaped comma", "\\,",
-                ",", null, ICON_UNDEFINED, ',');
-        assertParser("Single escaped letter", "\\a",
-                "a", null, ICON_UNDEFINED, 'a');
-        assertParser("Single at", "@",
-                "@", null, ICON_UNDEFINED, '@');
-        assertParser("Single escaped at", "\\@",
-                "@", null, ICON_UNDEFINED, '@');
-        assertParser("Single letter with outputText", "a|abc",
-                "a", "abc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with escaped outputText", "a|a\\|c",
-                "a", "a|c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with comma outputText", "a|a,b",
-                "a", "a,b", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with escaped comma outputText", "a|a\\,b",
-                "a", "a,b", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with outputText starts with at", "a|@bc",
-                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with outputText contains at", "a|a@c",
-                "a", "a@c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with escaped at outputText", "a|\\@bc",
-                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single escaped escape with outputText", "\\\\|\\\\",
-                "\\", "\\", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single escaped bar with outputText", "\\||\\|",
-                "|", "|", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Single letter with code", "a|" + CODE_SETTINGS,
-                "a", null, ICON_UNDEFINED, mCodeSettings);
-    }
-
-    public void testLabel() {
-        assertParser("Simple label", "abc",
-                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped bar", "a\\|c",
-                "a|c", "a|c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped escape", "a\\\\c",
-                "a\\c", "a\\c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with comma", "a,c",
-                "a,c", "a,c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped comma", "a\\,c",
-                "a,c", "a,c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label starts with at", "@bc",
-                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label contains at", "a@c",
-                "a@c", "a@c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped at", "\\@bc",
-                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped letter", "\\abc",
-                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with outputText", "abc|def",
-                "abc", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with comma and outputText", "a,c|def",
-                "a,c", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Escaped comma label with outputText", "a\\,c|def",
-                "a,c", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Escaped label with outputText", "a\\|c|def",
-                "a|c", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped bar outputText", "abc|d\\|f",
-                "abc", "d|f", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Escaped escape label with outputText", "a\\\\|def",
-                "a\\", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label starts with at and outputText", "@bc|def",
-                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label contains at label and outputText", "a@c|def",
-                "a@c", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Escaped at label with outputText", "\\@bc|def",
-                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with comma outputText", "abc|a,b",
-                "abc", "a,b", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped comma outputText", "abc|a\\,b",
-                "abc", "a,b", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with outputText starts with at", "abc|@bc",
-                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with outputText contains at", "abc|a@c",
-                "abc", "a@c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped at outputText", "abc|\\@bc",
-                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with escaped bar outputText", "abc|d\\|f",
-                "abc", "d|f", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Escaped bar label with escaped bar outputText", "a\\|c|d\\|f",
-                "a|c", "d|f", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParser("Label with code", "abc|" + CODE_SETTINGS,
-                "abc", null, ICON_UNDEFINED, mCodeSettings);
-        assertParser("Escaped label with code", "a\\|c|" + CODE_SETTINGS,
-                "a|c", null, ICON_UNDEFINED, mCodeSettings);
-    }
-
-    public void testIconAndCode() {
-        assertParser("Icon with outputText", ICON_SETTINGS + "|abc",
-                null, "abc", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
-        assertParser("Icon with outputText starts with at", ICON_SETTINGS + "|@bc",
-                null, "@bc", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
-        assertParser("Icon with outputText contains at", ICON_SETTINGS + "|a@c",
-                null, "a@c", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
-        assertParser("Icon with escaped at outputText", ICON_SETTINGS + "|\\@bc",
-                null, "@bc", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
-        assertParser("Label starts with at and code", "@bc|" + CODE_SETTINGS,
-                "@bc", null, ICON_UNDEFINED, mCodeSettings);
-        assertParser("Label contains at and code", "a@c|" + CODE_SETTINGS,
-                "a@c", null, ICON_UNDEFINED, mCodeSettings);
-        assertParser("Escaped at label with code", "\\@bc|" + CODE_SETTINGS,
-                "@bc", null, ICON_UNDEFINED, mCodeSettings);
-        assertParser("Icon with code", ICON_SETTINGS + "|" + CODE_SETTINGS,
-                null, null, ICON_SETTINGS_KEY, mCodeSettings);
-    }
-
-    public void testFormatError() {
-        assertParserError("Empty spec", "", null,
-                null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Empty label with outputText", "|a",
-                null, "a", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParserError("Empty label with code", "|" + CODE_SETTINGS,
-                null, null, ICON_UNDEFINED, mCodeSettings);
-        assertParserError("Empty outputText with label", "a|",
-                "a", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Empty outputText with icon", ICON_SETTINGS + "|",
-                null, null, ICON_SETTINGS_KEY, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Empty icon and code", "|",
-                null, null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Icon without code", ICON_SETTINGS,
-                null, null, ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
-        assertParser("Non existing icon", ICON_NON_EXISTING + "|abc",
-                null, "abc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
-        assertParserError("Non existing code", "abc|" + CODE_NON_EXISTING,
-                "abc", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Third bar at end", "a|b|",
-                "a", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Multiple bar", "a|b|c",
-                "a", null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Multiple bar with label and code", "a|" + CODE_SETTINGS + "|c",
-                "a", null, ICON_UNDEFINED, mCodeSettings);
-        assertParserError("Multiple bar with icon and outputText", ICON_SETTINGS + "|b|c",
-                null, null, ICON_SETTINGS_KEY, Keyboard.CODE_UNSPECIFIED);
-        assertParserError("Multiple bar with icon and code",
-                ICON_SETTINGS + "|" + CODE_SETTINGS + "|c",
-                null, null, ICON_SETTINGS_KEY, mCodeSettings);
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
new file mode 100644
index 0000000..81f744d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class BlueUnderlineTests extends InputTestsBase {
+
+    public void testBlueUnderline() {
+        final String STRING_TO_TYPE = "tgis";
+        final int EXPECTED_SPAN_START = 0;
+        final int EXPECTED_SPAN_END = 4;
+        type(STRING_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        final Span span = new Span(mTextView.getText());
+        assertEquals("show blue underline, span start", EXPECTED_SPAN_START, span.mStart);
+        assertEquals("show blue underline, span end", EXPECTED_SPAN_END, span.mEnd);
+        assertEquals("show blue underline, span color", true, span.isAutoCorrectionIndicator());
+    }
+
+    public void testBlueUnderlineDisappears() {
+        final String STRING_1_TO_TYPE = "tgis";
+        final String STRING_2_TO_TYPE = "q";
+        final int EXPECTED_SPAN_START = 0;
+        final int EXPECTED_SPAN_END = 5;
+        type(STRING_1_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        type(STRING_2_TO_TYPE);
+        // We haven't have time to look into the dictionary yet, so the line should still be
+        // blue to avoid any flicker.
+        final Span spanBefore = new Span(mTextView.getText());
+        assertEquals("extend blue underline, span start", EXPECTED_SPAN_START, spanBefore.mStart);
+        assertEquals("extend blue underline, span end", EXPECTED_SPAN_END, spanBefore.mEnd);
+        assertEquals("extend blue underline, span color", true,
+                spanBefore.isAutoCorrectionIndicator());
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        // Now we have been able to re-evaluate the word, there shouldn't be an auto-correction span
+        final Span spanAfter = new Span(mTextView.getText());
+        assertNull("hide blue underline", spanAfter.mSpan);
+    }
+
+    public void testBlueUnderlineOnBackspace() {
+        final String STRING_TO_TYPE = "tgis";
+        final int EXPECTED_SPAN_START = 0;
+        final int EXPECTED_SPAN_END = 4;
+        type(STRING_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        type(Keyboard.CODE_SPACE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        type(Keyboard.CODE_DELETE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        type(Keyboard.CODE_DELETE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        final Span span = new Span(mTextView.getText());
+        assertEquals("show blue underline after backspace, span start",
+                EXPECTED_SPAN_START, span.mStart);
+        assertEquals("show blue underline after backspace, span end",
+                EXPECTED_SPAN_END, span.mEnd);
+        assertEquals("show blue underline after backspace, span color", true,
+                span.isAutoCorrectionIndicator());
+    }
+
+    public void testBlueUnderlineDisappearsWhenCursorMoved() {
+        final String STRING_TO_TYPE = "tgis";
+        final int NEW_CURSOR_POSITION = 0;
+        type(STRING_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        // Simulate the onUpdateSelection() event
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        runMessages();
+        // Here the blue underline has been set. testBlueUnderline() is testing for this already,
+        // so let's not test it here again.
+        // Now simulate the user moving the cursor.
+        mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION);
+        mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        final Span span = new Span(mTextView.getText());
+        assertNull("blue underline removed when cursor is moved", span.mSpan);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java b/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
index 75bd049..c053a49 100644
--- a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
+++ b/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
@@ -37,7 +37,7 @@
      * sitting
      */
     public void testExample1() {
-        final int dist = Utils.editDistance("kitten", "sitting");
+        final int dist = BinaryDictionary.editDistance("kitten", "sitting");
         assertEquals("edit distance between 'kitten' and 'sitting' is 3",
                 3, dist);
     }
@@ -50,26 +50,26 @@
      * S--unday
      */
     public void testExample2() {
-        final int dist = Utils.editDistance("Saturday", "Sunday");
+        final int dist = BinaryDictionary.editDistance("Saturday", "Sunday");
         assertEquals("edit distance between 'Saturday' and 'Sunday' is 3",
                 3, dist);
     }
 
     public void testBothEmpty() {
-        final int dist = Utils.editDistance("", "");
+        final int dist = BinaryDictionary.editDistance("", "");
         assertEquals("when both string are empty, no edits are needed",
                 0, dist);
     }
 
     public void testFirstArgIsEmpty() {
-        final int dist = Utils.editDistance("", "aaaa");
+        final int dist = BinaryDictionary.editDistance("", "aaaa");
         assertEquals("when only one string of the arguments is empty,"
                  + " the edit distance is the length of the other.",
                  4, dist);
     }
 
     public void testSecoondArgIsEmpty() {
-        final int dist = Utils.editDistance("aaaa", "");
+        final int dist = BinaryDictionary.editDistance("aaaa", "");
         assertEquals("when only one string of the arguments is empty,"
                  + " the edit distance is the length of the other.",
                  4, dist);
@@ -78,27 +78,27 @@
     public void testSameStrings() {
         final String arg1 = "The quick brown fox jumps over the lazy dog.";
         final String arg2 = "The quick brown fox jumps over the lazy dog.";
-        final int dist = Utils.editDistance(arg1, arg2);
+        final int dist = BinaryDictionary.editDistance(arg1, arg2);
         assertEquals("when same strings are passed, distance equals 0.",
                 0, dist);
     }
 
     public void testSameReference() {
         final String arg = "The quick brown fox jumps over the lazy dog.";
-        final int dist = Utils.editDistance(arg, arg);
+        final int dist = BinaryDictionary.editDistance(arg, arg);
         assertEquals("when same string references are passed, the distance equals 0.",
                 0, dist);
     }
 
     public void testNullArg() {
         try {
-            Utils.editDistance(null, "aaa");
+            BinaryDictionary.editDistance(null, "aaa");
             fail("IllegalArgumentException should be thrown.");
         } catch (Exception e) {
             assertTrue(e instanceof IllegalArgumentException);
         }
         try {
-            Utils.editDistance("aaa", null);
+            BinaryDictionary.editDistance("aaa", null);
             fail("IllegalArgumentException should be thrown.");
         } catch (Exception e) {
             assertTrue(e instanceof IllegalArgumentException);
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicFrenchTests.java b/tests/src/com/android/inputmethod/latin/InputLogicFrenchTests.java
new file mode 100644
index 0000000..5db120d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputLogicFrenchTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class InputLogicFrenchTests extends InputTestsBase {
+
+    public void testAutoCorrectForFrench() {
+        final String STRING_TO_TYPE = "irq ";
+        final String EXPECTED_RESULT = "ira ";
+        changeLanguage("fr");
+        type(STRING_TO_TYPE);
+        assertEquals("simple auto-correct for French", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testManualPickThenSeparatorForFrench() {
+        final String WORD1_TO_TYPE = "test";
+        final String WORD2_TO_TYPE = "!";
+        final String EXPECTED_RESULT = "test !";
+        changeLanguage("fr");
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        type(WORD2_TO_TYPE);
+        assertEquals("manual pick then separator for French", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testWordThenSpaceThenPunctuationFromStripTwiceForFrench() {
+        final String WORD_TO_TYPE = "test ";
+        final String PUNCTUATION_FROM_STRIP = "!";
+        final String EXPECTED_RESULT = "test !!";
+        changeLanguage("fr");
+        type(WORD_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        assertTrue("type word then type space should display punctuation strip",
+                mLatinIME.isShowingPunctuationList());
+        mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
+        mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
+        assertEquals("type word then type space then punctuation from strip twice for French",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
new file mode 100644
index 0000000..11eb6ab
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class InputLogicTests extends InputTestsBase {
+
+    public void testTypeWord() {
+        final String WORD_TO_TYPE = "abcd";
+        type(WORD_TO_TYPE);
+        assertEquals("type word", WORD_TO_TYPE, mTextView.getText().toString());
+    }
+
+    public void testPickSuggestionThenBackspace() {
+        final String WORD_TO_TYPE = "this";
+        final String EXPECTED_RESULT = "this";
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("press suggestion then backspace", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testPickAutoCorrectionThenBackspace() {
+        final String WORD_TO_TYPE = "tgis";
+        final String WORD_TO_PICK = "this";
+        final String EXPECTED_RESULT = "tgis";
+        type(WORD_TO_TYPE);
+        // Choose the auto-correction, which is always in position 0. For "tgis", the
+        // auto-correction should be "this".
+        mLatinIME.pickSuggestionManually(0, WORD_TO_PICK);
+        mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
+        assertEquals("pick typed word over auto-correction then backspace", WORD_TO_PICK,
+                mTextView.getText().toString());
+        type(Keyboard.CODE_DELETE);
+        assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testPickTypedWordOverAutoCorrectionThenBackspace() {
+        final String WORD_TO_TYPE = "tgis";
+        final String EXPECTED_RESULT = "tgis";
+        type(WORD_TO_TYPE);
+        // Choose the typed word, which should be in position 1 (because position 0 should
+        // be occupied by the "this" auto-correction, as checked by testAutoCorrect())
+        mLatinIME.pickSuggestionManually(1, WORD_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
+        assertEquals("pick typed word over auto-correction then backspace", WORD_TO_TYPE,
+                mTextView.getText().toString());
+        type(Keyboard.CODE_DELETE);
+        assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testPickDifferentSuggestionThenBackspace() {
+        final String WORD_TO_TYPE = "tgis";
+        final String WORD_TO_PICK = "thus";
+        final String EXPECTED_RESULT = "tgis";
+        type(WORD_TO_TYPE);
+        // Choose the second suggestion, which should be in position 2 and should be "thus"
+        // when "tgis is typed.
+        mLatinIME.pickSuggestionManually(2, WORD_TO_PICK);
+        mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
+        assertEquals("pick different suggestion then backspace", WORD_TO_PICK,
+                mTextView.getText().toString());
+        type(Keyboard.CODE_DELETE);
+        assertEquals("pick different suggestion then backspace", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testDeleteSelection() {
+        final String STRING_TO_TYPE = "some text delete me some text";
+        final int SELECTION_START = 10;
+        final int SELECTION_END = 19;
+        final String EXPECTED_RESULT = "some text  some text";
+        type(STRING_TO_TYPE);
+        // There is no IMF to call onUpdateSelection for us so we must do it by hand.
+        // Send once to simulate the cursor actually responding to the move caused by typing.
+        // This is necessary because LatinIME is bookkeeping to avoid confusing a real cursor
+        // move with a move triggered by LatinIME inputting stuff.
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        mInputConnection.setSelection(SELECTION_START, SELECTION_END);
+        // And now we simulate the user actually selecting some text.
+        mLatinIME.onUpdateSelection(0, 0, SELECTION_START, SELECTION_END, -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testAutoCorrect() {
+        final String STRING_TO_TYPE = "tgis ";
+        final String EXPECTED_RESULT = "this ";
+        type(STRING_TO_TYPE);
+        assertEquals("simple auto-correct", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testAutoCorrectWithPeriod() {
+        final String STRING_TO_TYPE = "tgis.";
+        final String EXPECTED_RESULT = "this.";
+        type(STRING_TO_TYPE);
+        assertEquals("auto-correct with period", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testAutoCorrectWithPeriodThenRevert() {
+        final String STRING_TO_TYPE = "tgis.";
+        final String EXPECTED_RESULT = "tgis.";
+        type(STRING_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("auto-correct with period then revert", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testDoubleSpace() {
+        final String STRING_TO_TYPE = "this  ";
+        final String EXPECTED_RESULT = "this. ";
+        type(STRING_TO_TYPE);
+        assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testCancelDoubleSpace() {
+        final String STRING_TO_TYPE = "this  ";
+        final String EXPECTED_RESULT = "this  ";
+        type(STRING_TO_TYPE);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testBackspaceAtStartAfterAutocorrect() {
+        final String STRING_TO_TYPE = "tgis ";
+        final String EXPECTED_RESULT = "this ";
+        final int NEW_CURSOR_POSITION = 0;
+        type(STRING_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION);
+        mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("auto correct then move cursor to start of line then backspace",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testAutoCorrectThenMoveCursorThenBackspace() {
+        final String STRING_TO_TYPE = "and tgis ";
+        final String EXPECTED_RESULT = "andthis ";
+        final int NEW_CURSOR_POSITION = STRING_TO_TYPE.indexOf('t');
+        type(STRING_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION);
+        mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("auto correct then move cursor then backspace",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testNoSpaceAfterManualPick() {
+        final String WORD_TO_TYPE = "this";
+        final String EXPECTED_RESULT = WORD_TO_TYPE;
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        assertEquals("no space after manual pick", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testManualPickThenType() {
+        final String WORD1_TO_TYPE = "this";
+        final String WORD2_TO_TYPE = "is";
+        final String EXPECTED_RESULT = "this is";
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        type(WORD2_TO_TYPE);
+        assertEquals("manual pick then type", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testManualPickThenSeparator() {
+        final String WORD1_TO_TYPE = "this";
+        final String WORD2_TO_TYPE = "!";
+        final String EXPECTED_RESULT = "this!";
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        type(WORD2_TO_TYPE);
+        assertEquals("manual pick then separator", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testManualPickThenSpaceThenType() {
+        final String WORD1_TO_TYPE = "this";
+        final String WORD2_TO_TYPE = " is";
+        final String EXPECTED_RESULT = "this is";
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        type(WORD2_TO_TYPE);
+        assertEquals("manual pick then space then type", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testManualPickThenManualPick() {
+        final String WORD1_TO_TYPE = "this";
+        final String WORD2_TO_PICK = "is";
+        final String EXPECTED_RESULT = "this is";
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        // Here we fake picking a word through bigram prediction. This test is taking
+        // advantage of the fact that Latin IME blindly trusts the caller of #pickSuggestionManually
+        // to actually pass the right string.
+        mLatinIME.pickSuggestionManually(1, WORD2_TO_PICK);
+        assertEquals("manual pick then manual pick", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testDeleteWholeComposingWord() {
+        final String WORD_TO_TYPE = "this";
+        type(WORD_TO_TYPE);
+        for (int i = 0; i < WORD_TO_TYPE.length(); ++i) {
+            type(Keyboard.CODE_DELETE);
+        }
+        assertEquals("delete whole composing word", "", mTextView.getText().toString());
+    }
+    // TODO: Add some tests for non-BMP characters
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
new file mode 100644
index 0000000..5f6b229
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.preference.PreferenceManager;
+import android.test.ServiceTestCase;
+import android.text.InputType;
+import android.text.SpannableStringBuilder;
+import android.text.style.SuggestionSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
+
+import java.util.HashMap;
+
+public class InputTestsBase extends ServiceTestCase<LatinIME> {
+
+    private static final String PREF_DEBUG_MODE = "debug_mode";
+
+    // The message that sets the underline is posted with a 100 ms delay
+    protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200;
+
+    protected LatinIME mLatinIME;
+    protected Keyboard mKeyboard;
+    protected TextView mTextView;
+    protected InputConnection mInputConnection;
+    private final HashMap<String, InputMethodSubtype> mSubtypeMap =
+            new HashMap<String, InputMethodSubtype>();
+
+    // A helper class to ease span tests
+    public static class Span {
+        final SpannableStringBuilder mInputText;
+        final SuggestionSpan mSpan;
+        final int mStart;
+        final int mEnd;
+        // The supplied CharSequence should be an instance of SpannableStringBuilder,
+        // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception
+        // is thrown.
+        public Span(final CharSequence inputText) {
+            mInputText = (SpannableStringBuilder)inputText;
+            final SuggestionSpan[] spans =
+                    mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class);
+            if (0 == spans.length) {
+                mSpan = null;
+                mStart = -1;
+                mEnd = -1;
+            } else if (1 == spans.length) {
+                mSpan = spans[0];
+                mStart = mInputText.getSpanStart(mSpan);
+                mEnd = mInputText.getSpanEnd(mSpan);
+            } else {
+                throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length);
+            }
+        }
+        public boolean isAutoCorrectionIndicator() {
+            return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags());
+        }
+    }
+
+    public InputTestsBase() {
+        super(LatinIME.class);
+    }
+
+    // returns the previous setting value
+    protected boolean setDebugMode(final boolean mode) {
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
+        final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false);
+        final SharedPreferences.Editor editor = prefs.edit();
+        editor.putBoolean(PREF_DEBUG_MODE, true);
+        editor.commit();
+        return previousDebugSetting;
+    }
+
+    @Override
+    protected void setUp() {
+        try {
+            super.setUp();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        mTextView = new TextView(getContext());
+        mTextView.setInputType(InputType.TYPE_CLASS_TEXT);
+        mTextView.setEnabled(true);
+        setupService();
+        mLatinIME = getService();
+        final boolean previousDebugSetting = setDebugMode(true);
+        mLatinIME.onCreate();
+        setDebugMode(previousDebugSetting);
+        initSubtypeMap();
+        final EditorInfo ei = new EditorInfo();
+        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+        final InputConnection ic = mTextView.onCreateInputConnection(ei);
+        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+        final LayoutInflater inflater =
+                (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        final ViewGroup vg = new FrameLayout(getContext());
+        final View inputView = inflater.inflate(R.layout.input_view, vg);
+        mLatinIME.setInputView(inputView);
+        mLatinIME.onBindInput();
+        mLatinIME.onCreateInputView();
+        mLatinIME.onStartInputView(ei, false);
+        mLatinIME.onCreateInputMethodInterface().startInput(ic, ei);
+        mInputConnection = ic;
+        mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
+        changeLanguage("en_US");
+    }
+
+    private void initSubtypeMap() {
+        final InputMethodManager imm = (InputMethodManager)mLatinIME.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        final String packageName = mLatinIME.getPackageName();
+        for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+            if (imi.getPackageName().equals(packageName)) {
+                for (final InputMethodSubtype ims :
+                    imm.getEnabledInputMethodSubtypeList(imi, true)) {
+                    final String locale = ims.getLocale();
+                    mSubtypeMap.put(locale, ims);
+                }
+                return;
+            }
+        }
+        fail("LatinIME is disabled");
+    }
+
+    // We need to run the messages added to the handler from LatinIME. The only way to do
+    // that is to call Looper#loop() on the right looper, so we're going to get the looper
+    // object and call #loop() here. The messages in the handler actually run on the UI
+    // thread of the keyboard by design of the handler, so we want to call it synchronously
+    // on the same thread that the tests are running on to mimic the actual environment as
+    // closely as possible.
+    // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method
+    // is called, so we need to do that at the right time so that #loop() returns at some
+    // point and we don't end up in an infinite loop.
+    // After we quit, the looper is still technically ready to process more messages but
+    // the handler will refuse to enqueue any because #quit() has been called and it
+    // explicitly tests for it on message enqueuing, so we'll have to reset it so that
+    // it lets us continue normal operation.
+    protected void runMessages() {
+        // Here begins deep magic.
+        final Looper looper = mLatinIME.mHandler.getLooper();
+        mLatinIME.mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    looper.quit();
+                }
+            });
+        // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue).
+        // Once #quit() is called remaining messages are not processed, which is why we post
+        // a message that calls it instead of calling it directly.
+        looper.loop();
+
+        // Once #quit() has been called, the message queue has an "mQuiting" field that prevents
+        // any subsequent post in this queue. However the queue itself is still fully functional!
+        // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal,
+        // coming back to this method to run the messages.
+        MessageQueue queue = looper.getQueue();
+        try {
+            // However there is no way of doing it externally, and mQuiting is private.
+            // So... get out the big guns.
+            java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting");
+            f.setAccessible(true); // What do you mean "private"?
+            f.setBoolean(queue, false);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME.
+    protected void type(final int codePoint) {
+        // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the
+        // code (although multitouch/slide input and other factors make the sequencing complicated).
+        // They are supposed to be entirely deconnected from the input logic from LatinIME point of
+        // view and only delegates to the parts of the code that care. So we don't include them here
+        // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies,
+        // but keep them in mind if something breaks. Commenting them out as is should work.
+        //mLatinIME.onPressKey(codePoint);
+        for (final Key key : mKeyboard.mKeys) {
+            if (key.mCode == codePoint) {
+                final int x = key.mX + key.mWidth / 2;
+                final int y = key.mY + key.mHeight / 2;
+                mLatinIME.onCodeInput(codePoint, x, y);
+                return;
+            }
+        }
+        mLatinIME.onCodeInput(codePoint,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+        //mLatinIME.onReleaseKey(codePoint, false);
+    }
+
+    protected void type(final String stringToType) {
+        for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) {
+            type(stringToType.codePointAt(i));
+        }
+    }
+
+    protected void waitForDictionaryToBeLoaded() {
+        int remainingAttempts = 10;
+        while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) {
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                // Don't do much
+            } finally {
+                --remainingAttempts;
+            }
+        }
+        if (!mLatinIME.mSuggest.hasMainDictionary()) {
+            throw new RuntimeException("Can't initialize the main dictionary");
+        }
+    }
+
+    protected void changeLanguage(final String locale) {
+        final InputMethodSubtype subtype = mSubtypeMap.get(locale);
+        if (subtype == null) {
+            fail("InputMethodSubtype for locale " + locale + " is not enabled");
+        }
+        SubtypeSwitcher.getInstance().updateSubtype(subtype);
+        waitForDictionaryToBeLoaded();
+    }
+
+
+    // Helper to avoid writing the try{}catch block each time
+    protected static void sleep(final int milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {}
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
new file mode 100644
index 0000000..1b5b72f
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+public class PunctuationTests extends InputTestsBase {
+
+    public void testWordThenSpaceThenPunctuationFromStripTwice() {
+        final String WORD_TO_TYPE = "this ";
+        final String PUNCTUATION_FROM_STRIP = "!";
+        final String EXPECTED_RESULT = "this!! ";
+        type(WORD_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+        runMessages();
+        assertTrue("type word then type space should display punctuation strip",
+                mLatinIME.isShowingPunctuationList());
+        mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
+        mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
+        assertEquals("type word then type space then punctuation from strip twice", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testWordThenSpaceThenPunctuationFromKeyboardTwice() {
+        final String WORD_TO_TYPE = "this !!";
+        final String EXPECTED_RESULT = "this !!";
+        type(WORD_TO_TYPE);
+        assertEquals("manual pick then space then punctuation from keyboard twice", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testManualPickThenPunctuationFromStripTwiceThenType() {
+        final String WORD1_TO_TYPE = "this";
+        final String WORD2_TO_TYPE = "is";
+        final String PUNCTUATION_FROM_STRIP = "!";
+        final String EXPECTED_RESULT = "this!! is";
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
+        mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
+        type(WORD2_TO_TYPE);
+        assertEquals("pick word then pick punctuation twice then type", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testManualPickThenManualPickWithPunctAtStart() {
+        final String WORD1_TO_TYPE = "this";
+        final String WORD2_TO_PICK = "!is";
+        final String EXPECTED_RESULT = "this!is";
+        type(WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE);
+        mLatinIME.pickSuggestionManually(1, WORD2_TO_PICK);
+        assertEquals("manual pick then manual pick a word with punct at start", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testManuallyPickedWordThenColon() {
+        final String WORD_TO_TYPE = "this";
+        final String PUNCTUATION = ":";
+        final String EXPECTED_RESULT = "this:";
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        type(PUNCTUATION);
+        assertEquals("manually pick word then colon",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testManuallyPickedWordThenOpenParen() {
+        final String WORD_TO_TYPE = "this";
+        final String PUNCTUATION = "(";
+        final String EXPECTED_RESULT = "this (";
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        type(PUNCTUATION);
+        assertEquals("manually pick word then open paren",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testManuallyPickedWordThenCloseParen() {
+        final String WORD_TO_TYPE = "this";
+        final String PUNCTUATION = ")";
+        final String EXPECTED_RESULT = "this)";
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        type(PUNCTUATION);
+        assertEquals("manually pick word then close paren",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testManuallyPickedWordThenSmiley() {
+        final String WORD_TO_TYPE = "this";
+        final String SPECIAL_KEY = ":-)";
+        final String EXPECTED_RESULT = "this :-)";
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        mLatinIME.onTextInput(SPECIAL_KEY);
+        assertEquals("manually pick word then press the smiley key",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testManuallyPickedWordThenDotCom() {
+        final String WORD_TO_TYPE = "this";
+        final String SPECIAL_KEY = ".com";
+        final String EXPECTED_RESULT = "this.com";
+        type(WORD_TO_TYPE);
+        mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE);
+        mLatinIME.onTextInput(SPECIAL_KEY);
+        assertEquals("manually pick word then press the .com key",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testTypeWordTypeDotThenPressDotCom() {
+        final String WORD_TO_TYPE = "this.";
+        final String SPECIAL_KEY = ".com";
+        final String EXPECTED_RESULT = "this.com";
+        type(WORD_TO_TYPE);
+        mLatinIME.onTextInput(SPECIAL_KEY);
+        assertEquals("type word type dot then press the .com key",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testAutoCorrectionWithSingleQuoteInside() {
+        final String WORD_TO_TYPE = "you'f ";
+        final String EXPECTED_RESULT = "you'd ";
+        type(WORD_TO_TYPE);
+        assertEquals("auto-correction with single quote inside",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testAutoCorrectionWithSingleQuotesAround() {
+        final String WORD_TO_TYPE = "'tgis' ";
+        final String EXPECTED_RESULT = "'this' ";
+        type(WORD_TO_TYPE);
+        assertEquals("auto-correction with single quotes around",
+                EXPECTED_RESULT, mTextView.getText().toString());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index fec3e8e..089fdc5 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -16,10 +16,7 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.latin.LocaleUtils;
-
 import android.content.Context;
-import android.content.res.Resources;
 import android.test.AndroidTestCase;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -30,24 +27,22 @@
 import java.util.Locale;
 
 public class SubtypeLocaleTests extends AndroidTestCase {
-    private static final String PACKAGE = LatinIME.class.getPackage().getName();
-
-    private Resources mRes;
-    private List<InputMethodSubtype> mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
+    private List<InputMethodSubtype> mKeyboardSubtypes;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
         final Context context = getContext();
-        mRes = context.getResources();
+        final String packageName = context.getApplicationInfo().packageName;
 
         SubtypeLocale.init(context);
 
         final InputMethodManager imm = (InputMethodManager) context.getSystemService(
                 Context.INPUT_METHOD_SERVICE);
         for (final InputMethodInfo imi : imm.getInputMethodList()) {
-            if (imi.getPackageName().equals(PACKAGE)) {
+            if (imi.getPackageName().equals(packageName)) {
+                mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
                 final int subtypeCount = imi.getSubtypeCount();
                 for (int i = 0; i < subtypeCount; ++i) {
                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
@@ -58,37 +53,33 @@
                 break;
             }
         }
-        assertNotNull("Can not find input method " + PACKAGE, mKeyboardSubtypes);
+        assertNotNull("Can not find input method " + packageName, mKeyboardSubtypes);
         assertTrue("Can not find keyboard subtype", mKeyboardSubtypes.size() > 0);
     }
 
-    private String getStringWithLocale(int resId, Locale locale) {
-        final Locale savedLocale = Locale.getDefault();
-        try {
-            Locale.setDefault(locale);
-            return mRes.getString(resId);
-        } finally {
-            Locale.setDefault(savedLocale);
-        }
-    }
-
     public void testSubtypeLocale() {
         final StringBuilder messages = new StringBuilder();
         int failedCount = 0;
         for (final InputMethodSubtype subtype : mKeyboardSubtypes) {
-            final String localeCode = subtype.getLocale();
-            final Locale locale = LocaleUtils.constructLocaleFromString(localeCode);
-            // The locale name which will be displayed on spacebar.  For example 'English (US)' or
-            // 'Francais (Canada)'.  (c=\u008d)
-            final String displayName = SubtypeLocale.getFullDisplayName(locale);
-            // The subtype name in its locale.  For example 'English (US) Keyboard' or
-            // 'Clavier Francais (Canada)'.  (c=\u008d)
-            final String subtypeName = getStringWithLocale(subtype.getNameResId(), locale);
-            if (subtypeName.contains(displayName)) {
+            final Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
+            if (locale.getLanguage().equals("zz")) {
+                // This is special language name for language agnostic usage.
+                continue;
+            }
+            final String subtypeLocaleString =
+                    subtype.containsExtraValueKey(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
+                    ? subtype.getExtraValueOf(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
+                    : subtype.getLocale();
+            final Locale subtypeLocale = LocaleUtils.constructLocaleFromString(subtypeLocaleString);
+            // The subtype name in its locale.  For example 'English (US)' or 'Deutsch (QWERTY)'.
+            final String subtypeName = SubtypeLocale.getFullDisplayName(subtypeLocale);
+            // The locale language name in its locale.
+            final String languageName = locale.getDisplayLanguage(locale);
+            if (!subtypeName.contains(languageName)) {
                 failedCount++;
                 messages.append(String.format(
-                        "subtype name is '%s' and should contain locale '%s' name '%s'\n",
-                        subtypeName, localeCode, displayName));
+                        "subtype name is '%s' and should contain locale '%s' language name '%s'\n",
+                        subtypeName, subtypeLocale, languageName));
             }
         }
         assertEquals(messages.toString(), 0, failedCount);
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
deleted file mode 100644
index 464930f..0000000
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.text.TextUtils;
-
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.LatinKeyboard;
-
-import java.io.File;
-import java.util.Locale;
-
-public class SuggestHelper {
-    protected final Suggest mSuggest;
-    protected final LatinKeyboard mKeyboard;
-    private final KeyDetector mKeyDetector;
-
-    public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
-        // Use null as the locale for Suggest so as to force it to use the internal dictionary
-        // (and not try to find a dictionary provider for a specified locale)
-        mSuggest = new Suggest(context, dictionaryId, null);
-        mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build();
-        mKeyDetector = new KeyDetector(0);
-        init();
-    }
-
-    protected SuggestHelper(final Context context, final File dictionaryPath,
-            final long startOffset, final long length, final KeyboardId keyboardId,
-            final Locale locale) {
-        mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null, locale);
-        mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build();
-        mKeyDetector = new KeyDetector(0);
-        init();
-    }
-
-    private void init() {
-        mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
-        mKeyDetector.setKeyboard(mKeyboard, 0, 0);
-        mKeyDetector.setProximityCorrectionEnabled(true);
-        mKeyDetector.setProximityThreshold(mKeyboard.mMostCommonKeyWidth);
-    }
-
-    public void setCorrectionMode(int correctionMode) {
-        mSuggest.setCorrectionMode(correctionMode);
-    }
-
-    public boolean hasMainDictionary() {
-        return mSuggest.hasMainDictionary();
-    }
-
-    private void addKeyInfo(WordComposer word, char c) {
-        for (final Key key : mKeyboard.mKeys) {
-            if (key.mCode == c) {
-                final int x = key.mX + key.mWidth / 2;
-                final int y = key.mY + key.mHeight / 2;
-                final int[] codes = mKeyDetector.newCodeArray();
-                mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
-                word.add(c, codes, x, y);
-                return;
-            }
-        }
-        word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-    }
-
-    protected WordComposer createWordComposer(CharSequence s) {
-        WordComposer word = new WordComposer();
-        for (int i = 0; i < s.length(); i++) {
-            final char c = s.charAt(i);
-            addKeyInfo(word, c);
-        }
-        return word;
-    }
-
-    public boolean isValidWord(CharSequence typed) {
-        return AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(),
-                typed, false);
-    }
-
-    // TODO: This may be slow, but is OK for test so far.
-    public SuggestedWords getSuggestions(CharSequence typed) {
-        return mSuggest.getSuggestions(createWordComposer(typed), null,
-                mKeyboard.getProximityInfo());
-    }
-
-    public CharSequence getFirstSuggestion(CharSequence typed) {
-        WordComposer word = createWordComposer(typed);
-        SuggestedWords suggestions = mSuggest.getSuggestions(word, null,
-                mKeyboard.getProximityInfo());
-        // Note that suggestions.getWord(0) is the word user typed.
-        return suggestions.size() > 1 ? suggestions.getWord(1) : null;
-    }
-
-    public CharSequence getAutoCorrection(CharSequence typed) {
-        WordComposer word = createWordComposer(typed);
-        SuggestedWords suggestions = mSuggest.getSuggestions(word, null,
-                mKeyboard.getProximityInfo());
-        // Note that suggestions.getWord(0) is the word user typed.
-        return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
-                ? suggestions.getWord(1) : null;
-    }
-
-    public int getSuggestIndex(CharSequence typed, CharSequence expected) {
-        WordComposer word = createWordComposer(typed);
-        SuggestedWords suggestions = mSuggest.getSuggestions(word, null,
-                mKeyboard.getProximityInfo());
-        // Note that suggestions.getWord(0) is the word user typed.
-        for (int i = 1; i < suggestions.size(); i++) {
-            if (TextUtils.equals(suggestions.getWord(i), expected))
-                return i;
-        }
-        return -1;
-    }
-
-    private void getBigramSuggestions(CharSequence previous, CharSequence typed) {
-        if (!TextUtils.isEmpty(previous) && (typed.length() > 1)) {
-            WordComposer firstChar = createWordComposer(Character.toString(typed.charAt(0)));
-            mSuggest.getSuggestions(firstChar, previous, mKeyboard.getProximityInfo());
-        }
-    }
-
-    public CharSequence getBigramFirstSuggestion(CharSequence previous, CharSequence typed) {
-        WordComposer word = createWordComposer(typed);
-        getBigramSuggestions(previous, typed);
-        SuggestedWords suggestions = mSuggest.getSuggestions(word, previous,
-                mKeyboard.getProximityInfo());
-        return suggestions.size() > 1 ? suggestions.getWord(1) : null;
-    }
-
-    public CharSequence getBigramAutoCorrection(CharSequence previous, CharSequence typed) {
-        WordComposer word = createWordComposer(typed);
-        getBigramSuggestions(previous, typed);
-        SuggestedWords suggestions = mSuggest.getSuggestions(word, previous,
-                mKeyboard.getProximityInfo());
-        return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
-                ? suggestions.getWord(1) : null;
-    }
-
-    public int searchBigramSuggestion(CharSequence previous, CharSequence typed,
-            CharSequence expected) {
-        WordComposer word = createWordComposer(typed);
-        getBigramSuggestions(previous, typed);
-        SuggestedWords suggestions = mSuggest.getSuggestions(word, previous,
-                mKeyboard.getProximityInfo());
-        for (int i = 1; i < suggestions.size(); i++) {
-            if (TextUtils.equals(suggestions.getWord(i), expected))
-                return i;
-        }
-        return -1;
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTests.java b/tests/src/com/android/inputmethod/latin/SuggestTests.java
deleted file mode 100644
index 4080f34..0000000
--- a/tests/src/com/android/inputmethod/latin/SuggestTests.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2010,2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import com.android.inputmethod.latin.tests.R;
-
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Configuration;
-
-import java.util.Locale;
-
-public class SuggestTests extends SuggestTestsBase {
-    private SuggestHelper mHelper;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test);
-        final Locale locale = Locale.US;
-        mHelper = new SuggestHelper(
-                getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
-                createKeyboardId(locale, Configuration.ORIENTATION_PORTRAIT), locale);
-        mHelper.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
-    }
-
-    /************************** Tests ************************/
-
-    /**
-     * Tests for simple completions of one character.
-     */
-    public void testCompletion1char() {
-        suggested("people", mHelper.getFirstSuggestion("peopl"));
-        suggested("about", mHelper.getFirstSuggestion("abou"));
-        suggested("their", mHelper.getFirstSuggestion("thei"));
-    }
-
-    /**
-     * Tests for simple completions of two characters.
-     */
-    public void testCompletion2char() {
-        suggested("people", mHelper.getFirstSuggestion("peop"));
-        suggested("calling", mHelper.getFirstSuggestion("calli"));
-        suggested("business", mHelper.getFirstSuggestion("busine"));
-    }
-
-    /**
-     * Tests for proximity errors.
-     */
-    public void testProximityPositive() {
-        suggested("typed peiple", "people", mHelper.getFirstSuggestion("peiple"));
-        suggested("typed peoole", "people", mHelper.getFirstSuggestion("peoole"));
-        suggested("typed pwpple", "people", mHelper.getFirstSuggestion("pwpple"));
-    }
-
-    /**
-     * Tests for proximity errors - negative, when the error key is not close.
-     */
-    public void testProximityNegative() {
-        notSuggested("about", mHelper.getFirstSuggestion("arout"));
-        notSuggested("are", mHelper.getFirstSuggestion("ire"));
-    }
-
-    /**
-     * Tests for checking if apostrophes are added automatically.
-     */
-    public void testApostropheInsertion() {
-        suggested("I'm", mHelper.getFirstSuggestion("im"));
-        suggested("don't", mHelper.getFirstSuggestion("dont"));
-    }
-
-    /**
-     * Test to make sure apostrophed word is not suggested for an apostrophed word.
-     */
-    public void testApostrophe() {
-        notSuggested("don't", mHelper.getFirstSuggestion("don't"));
-    }
-
-    /**
-     * Tests for suggestion of capitalized version of a word.
-     */
-    public void testCapitalization() {
-        suggested("I'm", mHelper.getFirstSuggestion("i'm"));
-        suggested("Sunday", mHelper.getFirstSuggestion("sunday"));
-        suggested("Sunday", mHelper.getFirstSuggestion("sundat"));
-    }
-
-    /**
-     * Tests to see if more than one completion is provided for certain prefixes.
-     */
-    public void testMultipleCompletions() {
-        isInSuggestions("com: come", mHelper.getSuggestIndex("com", "come"));
-        isInSuggestions("com: company", mHelper.getSuggestIndex("com", "company"));
-        isInSuggestions("th: the", mHelper.getSuggestIndex("th", "the"));
-        isInSuggestions("th: that", mHelper.getSuggestIndex("th", "that"));
-        isInSuggestions("th: this", mHelper.getSuggestIndex("th", "this"));
-        isInSuggestions("th: they", mHelper.getSuggestIndex("th", "they"));
-    }
-
-    /**
-     * Does the suggestion engine recognize zero frequency words as valid words.
-     */
-    public void testZeroFrequencyAccepted() {
-        assertTrue("valid word yikes", mHelper.isValidWord("yikes"));
-        assertFalse("non valid word yike", mHelper.isValidWord("yike"));
-    }
-
-    /**
-     * Tests to make sure that zero frequency words are not suggested as completions.
-     */
-    public void testZeroFrequencySuggestionsNegative() {
-        assertTrue(mHelper.getSuggestIndex("yike", "yikes") < 0);
-        assertTrue(mHelper.getSuggestIndex("what", "whatcha") < 0);
-    }
-
-    /**
-     * Tests to ensure that words with large edit distances are not suggested, in some cases.
-     * Also such word is not considered auto correction, in some cases.
-     */
-    public void testTooLargeEditDistance() {
-        assertTrue(mHelper.getSuggestIndex("sniyr", "about") < 0);
-        // TODO: The following test fails.
-        // notSuggested("the", mHelper.getAutoCorrection("rjw"));
-    }
-
-    /**
-     * Make sure mHelper.isValidWord is case-sensitive.
-     */
-    public void testValidityCaseSensitivity() {
-        assertTrue("valid word Sunday", mHelper.isValidWord("Sunday"));
-        assertFalse("non valid word sunday", mHelper.isValidWord("sunday"));
-    }
-
-    /**
-     * Are accented forms of words suggested as corrections?
-     */
-    public void testAccents() {
-        // ni<LATIN SMALL LETTER N WITH TILDE>o
-        suggested("ni\u00F1o", mHelper.getAutoCorrection("nino"));
-        // ni<LATIN SMALL LETTER N WITH TILDE>o
-        suggested("ni\u00F1o", mHelper.getAutoCorrection("nimo"));
-        // Mar<LATIN SMALL LETTER I WITH ACUTE>a
-        suggested("Mar\u00EDa", mHelper.getAutoCorrection("maria"));
-    }
-
-    /**
-     * Make sure bigrams are showing when first character is typed
-     *  and don't show any when there aren't any
-     */
-    public void testBigramsAtFirstChar() {
-        suggested("bigram: about p[art]",
-                "part", mHelper.getBigramFirstSuggestion("about", "p"));
-        suggested("bigram: I'm a[bout]",
-                "about", mHelper.getBigramFirstSuggestion("I'm", "a"));
-        suggested("bigram: about b[usiness]",
-                "business", mHelper.getBigramFirstSuggestion("about", "b"));
-        isInSuggestions("bigram: about b[eing]",
-                mHelper.searchBigramSuggestion("about", "b", "being"));
-        notSuggested("bigram: about p",
-                "business", mHelper.getBigramFirstSuggestion("about", "p"));
-    }
-
-    /**
-     * Make sure bigrams score affects the original score
-     */
-    public void testBigramsScoreEffect() {
-        suggested("single: page",
-                "page", mHelper.getAutoCorrection("pa"));
-        suggested("bigram: about pa[rt]",
-                "part", mHelper.getBigramAutoCorrection("about", "pa"));
-        // TODO: The following test fails.
-        // suggested("single: said", "said", mHelper.getAutoCorrection("sa"));
-        suggested("bigram: from sa[me]",
-                "same", mHelper.getBigramAutoCorrection("from", "sa"));
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
deleted file mode 100644
index 058a3e7..0000000
--- a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Configuration;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.inputmethod.EditorInfo;
-
-import com.android.inputmethod.keyboard.KeyboardId;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.Locale;
-
-public class SuggestTestsBase extends AndroidTestCase {
-    protected File mTestPackageFile;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mTestPackageFile = new File(getTestContext().getApplicationInfo().sourceDir);
-    }
-
-    protected KeyboardId createKeyboardId(Locale locale, int orientation) {
-        final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
-        final int width;
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            width = Math.max(dm.widthPixels, dm.heightPixels);
-        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-            width = Math.min(dm.widthPixels, dm.heightPixels);
-        } else {
-            fail("Orientation should be ORIENTATION_LANDSCAPE or ORIENTATION_PORTRAIT: "
-                    + "orientation=" + orientation);
-            return null;
-        }
-        return new KeyboardId(locale.toString() + " keyboard",
-                com.android.inputmethod.latin.R.xml.kbd_qwerty, locale, orientation, width,
-                KeyboardId.MODE_TEXT, new EditorInfo(), false, KeyboardId.F2KEY_MODE_NONE,
-                false, false, false);
-    }
-
-    protected InputStream openTestRawResource(int resIdInTest) {
-        return getTestContext().getResources().openRawResource(resIdInTest);
-    }
-
-    protected AssetFileDescriptor openTestRawResourceFd(int resIdInTest) {
-        return getTestContext().getResources().openRawResourceFd(resIdInTest);
-    }
-
-    private static String format(String message, Object expected, Object actual) {
-        return message + " expected:<" + expected + "> but was:<" + actual + ">";
-    }
-
-    protected static void suggested(CharSequence expected, CharSequence actual) {
-        if (!TextUtils.equals(expected, actual))
-            fail(format("assertEquals", expected, actual));
-    }
-
-    protected static void suggested(String message, CharSequence expected, CharSequence actual) {
-        if (!TextUtils.equals(expected, actual))
-            fail(format(message, expected, actual));
-    }
-
-    protected static void notSuggested(CharSequence expected, CharSequence actual) {
-        if (TextUtils.equals(expected, actual))
-            fail(format("assertNotEquals", expected, actual));
-    }
-
-    protected static void notSuggested(String message, CharSequence expected, CharSequence actual) {
-        if (TextUtils.equals(expected, actual))
-            fail(format(message, expected, actual));
-    }
-
-    protected static void isInSuggestions(String message, int position) {
-        assertTrue(message, position >= 0);
-    }
-
-    protected static void isNotInSuggestions(String message, int position) {
-        assertTrue(message, position < 0);
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
deleted file mode 100644
index 023e20a..0000000
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import com.android.inputmethod.keyboard.KeyboardId;
-
-import android.content.Context;
-import android.text.TextUtils;
-
-import java.io.File;
-import java.util.Locale;
-import java.util.StringTokenizer;
-
-public class UserBigramSuggestHelper extends SuggestHelper {
-    private final Context mContext;
-    private UserBigramDictionary mUserBigram;
-
-    public UserBigramSuggestHelper(final Context context, final File dictionaryPath,
-            final long startOffset, final long length, final int userBigramMax,
-            final int userBigramDelete, final KeyboardId keyboardId, final Locale locale) {
-        super(context, dictionaryPath, startOffset, length, keyboardId, locale);
-        mContext = context;
-        mUserBigram = new UserBigramDictionary(context, null, locale.toString(),
-                Suggest.DIC_USER);
-        mUserBigram.setDatabaseMax(userBigramMax);
-        mUserBigram.setDatabaseDelete(userBigramDelete);
-        mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
-        mSuggest.setUserBigramDictionary(mUserBigram);
-    }
-
-    public void changeUserBigramLocale(Locale locale) {
-        if (mUserBigram != null) {
-            flushUserBigrams();
-            mUserBigram.close();
-            mUserBigram = new UserBigramDictionary(mContext, null, locale.toString(),
-                    Suggest.DIC_USER);
-            mSuggest.setUserBigramDictionary(mUserBigram);
-        }
-    }
-
-    public int searchUserBigramSuggestion(CharSequence previous, char typed,
-            CharSequence expected) {
-        if (mUserBigram == null) return -1;
-
-        flushUserBigrams();
-        if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
-            WordComposer firstChar = createWordComposer(Character.toString(typed));
-            mSuggest.getSuggestions(firstChar, previous, mKeyboard.getProximityInfo());
-            boolean reloading = mUserBigram.reloadDictionaryIfRequired();
-            if (reloading) mUserBigram.waitForDictionaryLoading();
-            mUserBigram.getBigrams(firstChar, previous, mSuggest);
-        }
-
-        for (int i = 0; i < mSuggest.mBigramSuggestions.size(); i++) {
-            final CharSequence word = mSuggest.mBigramSuggestions.get(i);
-            if (TextUtils.equals(word, expected))
-                return i;
-        }
-
-        return -1;
-    }
-
-    public void addToUserBigram(String sentence) {
-        StringTokenizer st = new StringTokenizer(sentence);
-        String previous = null;
-        while (st.hasMoreTokens()) {
-            String current = st.nextToken();
-            if (previous != null) {
-                addToUserBigram(new String[] {previous, current});
-            }
-            previous = current;
-        }
-    }
-
-    public void addToUserBigram(String[] pair) {
-        if (mUserBigram != null && pair.length == 2) {
-            mUserBigram.addBigrams(pair[0], pair[1]);
-        }
-    }
-
-    public void flushUserBigrams() {
-        if (mUserBigram != null) {
-            mUserBigram.flushPendingWrites();
-            mUserBigram.waitUntilUpdateDBDone();
-        }
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
deleted file mode 100644
index 2bc0aab..0000000
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2010,2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Configuration;
-
-import com.android.inputmethod.latin.tests.R;
-
-import java.util.Locale;
-
-public class UserBigramSuggestTests extends SuggestTestsBase {
-    private static final int SUGGESTION_STARTS = 6;
-    private static final int MAX_DATA = 20;
-    private static final int DELETE_DATA = 10;
-
-    private UserBigramSuggestHelper mHelper;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test);
-        final Locale locale = Locale.US;
-        mHelper = new UserBigramSuggestHelper(
-                getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
-                MAX_DATA, DELETE_DATA,
-                createKeyboardId(locale, Configuration.ORIENTATION_PORTRAIT), locale);
-    }
-
-    /************************** Tests ************************/
-
-    /**
-     * Test suggestion started at right time
-     */
-    public void testUserBigram() {
-        for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair1);
-        for (int i = 0; i < (SUGGESTION_STARTS - 1); i++) mHelper.addToUserBigram(pair2);
-
-        isInSuggestions("bigram", mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
-        isNotInSuggestions("platform",
-                mHelper.searchUserBigramSuggestion("android", 'p', "platform"));
-    }
-
-    /**
-     * Test loading correct (locale) bigrams
-     */
-    public void testOpenAndClose() {
-        for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair1);
-        isInSuggestions("bigram in default locale",
-                mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
-
-        // change to fr_FR
-        mHelper.changeUserBigramLocale(Locale.FRANCE);
-        for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair3);
-        isInSuggestions("france in fr_FR",
-                mHelper.searchUserBigramSuggestion("locale", 'f', "france"));
-        isNotInSuggestions("bigram in fr_FR",
-                mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
-
-        // change back to en_US
-        mHelper.changeUserBigramLocale(Locale.US);
-        isNotInSuggestions("france in en_US",
-                mHelper.searchUserBigramSuggestion("locale", 'f', "france"));
-        isInSuggestions("bigram in en_US",
-                mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
-    }
-
-    /**
-     * Test data gets pruned when it is over maximum
-     */
-    public void testPruningData() {
-        for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(sentence0);
-        mHelper.flushUserBigrams();
-        isInSuggestions("world after several sentence 0",
-                mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
-
-        mHelper.addToUserBigram(sentence1);
-        mHelper.addToUserBigram(sentence2);
-        isInSuggestions("world after sentence 1 and 2",
-                mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
-
-        // pruning should happen
-        mHelper.addToUserBigram(sentence3);
-        mHelper.addToUserBigram(sentence4);
-
-        // trying to reopen database to check pruning happened in database
-        mHelper.changeUserBigramLocale(Locale.US);
-        isNotInSuggestions("world after sentence 3 and 4",
-                mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
-    }
-
-    private static final String[] pair1 = {"user", "bigram"};
-    private static final String[] pair2 = {"android","platform"};
-    private static final String[] pair3 = {"locale", "france"};
-    private static final String sentence0 = "Hello world";
-    private static final String sentence1 = "This is a test for user input based bigram";
-    private static final String sentence2 = "It learns phrases that contain both dictionary and "
-        + "nondictionary words";
-    private static final String sentence3 = "This should give better suggestions than the previous "
-        + "version";
-    private static final String sentence4 = "Android stock keyboard is improving";
-}
diff --git a/tests/src/com/android/inputmethod/latin/UtilsTests.java b/tests/src/com/android/inputmethod/latin/UtilsTests.java
index 5c0b03a..2ef4e2f 100644
--- a/tests/src/com/android/inputmethod/latin/UtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/UtilsTests.java
@@ -18,8 +18,6 @@
 
 import android.test.AndroidTestCase;
 
-import com.android.inputmethod.latin.tests.R;
-
 public class UtilsTests extends AndroidTestCase {
 
     // The following is meant to be a reasonable default for
diff --git a/tools/Android.mk b/tools/Android.mk
index 8f1acc5..91b2fbb 100644
--- a/tools/Android.mk
+++ b/tools/Android.mk
@@ -12,6 +12,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-subdir-makefiles)
diff --git a/tools/makedict/Android.mk b/tools/makedict/Android.mk
index 6832b1c..dcfad19 100644
--- a/tools/makedict/Android.mk
+++ b/tools/makedict/Android.mk
@@ -12,11 +12,14 @@
 # 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.
-#
+
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
+MAKEDICT_CORE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod/latin/makedict
+
+LOCAL_SRC_FILES := $(call all-java-files-under,$(MAKEDICT_CORE_SOURCE_DIRECTORY))
+LOCAL_SRC_FILES += $(call all-java-files-under,src)
 LOCAL_SRC_FILES += $(call all-java-files-under,tests)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE_TAGS := eng
diff --git a/tools/makedict/etc/manifest.txt b/tools/makedict/etc/manifest.txt
index 948609d..4f085e7 100644
--- a/tools/makedict/etc/manifest.txt
+++ b/tools/makedict/etc/manifest.txt
@@ -1 +1 @@
-Main-Class: com.android.inputmethod.latin.DictionaryMaker
+Main-Class: com.android.inputmethod.latin.makedict.DictionaryMaker
diff --git a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
deleted file mode 100644
index 35a7b51..0000000
--- a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import com.android.inputmethod.latin.FusionDictionary.WeightedString;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.TreeSet;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Reads and writes XML files for a FusionDictionary.
- *
- * All functions in this class are static.
- */
-public class XmlDictInputOutput {
-
-    private static final String WORD_TAG = "w";
-    private static final String BIGRAM_TAG = "bigram";
-    private static final String FREQUENCY_ATTR = "f";
-    private static final String WORD_ATTR = "word";
-
-    /**
-     * SAX handler for a unigram XML file.
-     */
-    static private class UnigramHandler extends DefaultHandler {
-        // Parser states
-        private static final int NONE = 0;
-        private static final int START = 1;
-        private static final int WORD = 2;
-        private static final int BIGRAM = 4;
-        private static final int END = 5;
-        private static final int UNKNOWN = 6;
-
-        final FusionDictionary mDictionary;
-        int mState; // the state of the parser
-        int mFreq; // the currently read freq
-        String mWord; // the current word
-        final HashMap<String, ArrayList<WeightedString>> mBigramsMap;
-
-        /**
-         * Create the handler.
-         *
-         * @param dict the dictionary to construct.
-         * @param bigrams the bigrams as a map. This may be empty, but may not be null.
-         */
-        public UnigramHandler(FusionDictionary dict,
-                HashMap<String, ArrayList<WeightedString>> bigrams) {
-            mDictionary = dict;
-            mBigramsMap = bigrams;
-            mWord = "";
-            mState = START;
-            mFreq = 0;
-        }
-
-        @Override
-        public void startElement(String uri, String localName, String qName, Attributes attrs) {
-            if (WORD_TAG.equals(localName)) {
-                mState = WORD;
-                mWord = "";
-                for (int attrIndex = 0; attrIndex < attrs.getLength(); ++attrIndex) {
-                    final String attrName = attrs.getLocalName(attrIndex);
-                    if (FREQUENCY_ATTR.equals(attrName)) {
-                        mFreq = Integer.parseInt(attrs.getValue(attrIndex));
-                    }
-                }
-            } else {
-                mState = UNKNOWN;
-            }
-        }
-
-        @Override
-        public void characters(char[] ch, int start, int length) {
-            if (WORD == mState) {
-                // The XML parser is free to return text in arbitrary chunks one after the
-                // other. In particular, this happens in some implementations when it finds
-                // an escape code like "&amp;".
-                mWord += String.copyValueOf(ch, start, length);
-            }
-        }
-
-        @Override
-        public void endElement(String uri, String localName, String qName) {
-            if (WORD == mState) {
-                mDictionary.add(mWord, mFreq, mBigramsMap.get(mWord));
-                mState = START;
-            }
-        }
-    }
-
-    /**
-     * SAX handler for a bigram XML file.
-     */
-    static private class BigramHandler extends DefaultHandler {
-        private final static String BIGRAM_W1_TAG = "bi";
-        private final static String BIGRAM_W2_TAG = "w";
-        private final static String BIGRAM_W1_ATTRIBUTE = "w1";
-        private final static String BIGRAM_W2_ATTRIBUTE = "w2";
-        private final static String BIGRAM_FREQ_ATTRIBUTE = "p";
-
-        String mW1;
-        final HashMap<String, ArrayList<WeightedString>> mBigramsMap;
-
-        public BigramHandler() {
-            mW1 = null;
-            mBigramsMap = new HashMap<String, ArrayList<WeightedString>>();
-        }
-
-        @Override
-        public void startElement(String uri, String localName, String qName, Attributes attrs) {
-            if (BIGRAM_W1_TAG.equals(localName)) {
-                mW1 = attrs.getValue(uri, BIGRAM_W1_ATTRIBUTE);
-            } else if (BIGRAM_W2_TAG.equals(localName)) {
-                String w2 = attrs.getValue(uri, BIGRAM_W2_ATTRIBUTE);
-                int freq = Integer.parseInt(attrs.getValue(uri, BIGRAM_FREQ_ATTRIBUTE));
-                WeightedString bigram = new WeightedString(w2, freq / 8);
-                ArrayList<WeightedString> bigramList = mBigramsMap.get(mW1);
-                if (null == bigramList) bigramList = new ArrayList<WeightedString>();
-                bigramList.add(bigram);
-                mBigramsMap.put(mW1, bigramList);
-            }
-        }
-
-        public HashMap<String, ArrayList<WeightedString>> getBigramMap() {
-            return mBigramsMap;
-        }
-    }
-
-    /**
-     * Reads a dictionary from an XML file.
-     *
-     * This is the public method that will parse an XML file and return the corresponding memory
-     * representation.
-     *
-     * @param unigrams the file to read the data from.
-     * @return the in-memory representation of the dictionary.
-     */
-    public static FusionDictionary readDictionaryXml(InputStream unigrams, InputStream bigrams)
-            throws SAXException, IOException, ParserConfigurationException {
-        final SAXParserFactory factory = SAXParserFactory.newInstance();
-        factory.setNamespaceAware(true);
-        final SAXParser parser = factory.newSAXParser();
-        final BigramHandler bigramHandler = new BigramHandler();
-        if (null != bigrams) parser.parse(bigrams, bigramHandler);
-
-        final FusionDictionary dict = new FusionDictionary();
-        final UnigramHandler unigramHandler =
-                new UnigramHandler(dict, bigramHandler.getBigramMap());
-        parser.parse(unigrams, unigramHandler);
-        return dict;
-    }
-
-    /**
-     * Reads a dictionary in the first, legacy XML format
-     *
-     * This method reads data from the parser and creates a new FusionDictionary with it.
-     * The format parsed by this method is the format used before Ice Cream Sandwich,
-     * which has no support for bigrams or shortcuts.
-     * It is important to note that this method expects the parser to have already eaten
-     * the first, all-encompassing tag.
-     *
-     * @param xpp the parser to read the data from.
-     * @return the parsed dictionary.
-     */
-
-    /**
-     * Writes a dictionary to an XML file.
-     *
-     * The output format is the "second" format, which supports bigrams and shortcuts.
-     *
-     * @param destination a destination stream to write to.
-     * @param dict the dictionary to write.
-     */
-    public static void writeDictionaryXml(Writer destination, FusionDictionary dict)
-            throws IOException {
-        final TreeSet<Word> set = new TreeSet<Word>();
-        for (Word word : dict) {
-            set.add(word);
-        }
-        // TODO: use an XMLSerializer if this gets big
-        destination.write("<wordlist format=\"2\">\n");
-        for (Word word : set) {
-            destination.write("  <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" "
-                    + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\">");
-            if (null != word.mBigrams) {
-                destination.write("\n");
-                for (WeightedString bigram : word.mBigrams) {
-                    destination.write("    <" + BIGRAM_TAG + " " + FREQUENCY_ATTR + "=\""
-                            + bigram.mFrequency + "\">" + bigram.mWord + "</" + BIGRAM_TAG + ">\n");
-                }
-                destination.write("  ");
-            }
-            destination.write("</" + WORD_TAG + ">\n");
-        }
-        destination.write("</wordlist>\n");
-        destination.close();
-    }
-}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java b/tools/makedict/src/com/android/inputmethod/latin/makedict/DictionaryMaker.java
similarity index 68%
rename from tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
rename to tools/makedict/src/com/android/inputmethod/latin/makedict/DictionaryMaker.java
index 1ba0107..5e39215 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/makedict/DictionaryMaker.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -39,18 +39,37 @@
         private final static String OPTION_VERSION_2 = "-2";
         private final static String OPTION_INPUT_SOURCE = "-s";
         private final static String OPTION_INPUT_BIGRAM_XML = "-b";
+        private final static String OPTION_INPUT_SHORTCUT_XML = "-c";
         private final static String OPTION_OUTPUT_BINARY = "-d";
+        private final static String OPTION_OUTPUT_BINARY_FORMAT_VERSION_1 = "-d1";
         private final static String OPTION_OUTPUT_XML = "-x";
         private final static String OPTION_HELP = "-h";
         public final String mInputBinary;
         public final String mInputUnigramXml;
+        public final String mInputShortcutXml;
         public final String mInputBigramXml;
         public final String mOutputBinary;
+        public final String mOutputBinaryFormat1;
         public final String mOutputXml;
 
-        private void checkIntegrity() {
+        private void checkIntegrity() throws IOException {
             checkHasExactlyOneInput();
             checkHasAtLeastOneOutput();
+            checkNotSameFile(mInputBinary, mOutputBinary);
+            checkNotSameFile(mInputBinary, mOutputBinaryFormat1);
+            checkNotSameFile(mInputBinary, mOutputXml);
+            checkNotSameFile(mInputUnigramXml, mOutputBinary);
+            checkNotSameFile(mInputUnigramXml, mOutputBinaryFormat1);
+            checkNotSameFile(mInputUnigramXml, mOutputXml);
+            checkNotSameFile(mInputShortcutXml, mOutputBinary);
+            checkNotSameFile(mInputShortcutXml, mOutputBinaryFormat1);
+            checkNotSameFile(mInputShortcutXml, mOutputXml);
+            checkNotSameFile(mInputBigramXml, mOutputBinary);
+            checkNotSameFile(mInputBigramXml, mOutputBinaryFormat1);
+            checkNotSameFile(mInputBigramXml, mOutputXml);
+            checkNotSameFile(mOutputBinary, mOutputBinaryFormat1);
+            checkNotSameFile(mOutputBinary, mOutputXml);
+            checkNotSameFile(mOutputBinaryFormat1, mOutputXml);
         }
 
         private void checkHasExactlyOneInput() {
@@ -65,33 +84,50 @@
         }
 
         private void checkHasAtLeastOneOutput() {
-            if (null == mOutputBinary && null == mOutputXml) {
+            if (null == mOutputBinary && null == mOutputBinaryFormat1 && null == mOutputXml) {
                 throw new RuntimeException("No output specified");
             }
         }
 
+        /**
+         * Utility method that throws an exception if path1 and path2 point to the same file.
+         */
+        private static void checkNotSameFile(final String path1, final String path2)
+                throws IOException {
+            if (null == path1 || null == path2) return;
+            if (new File(path1).getCanonicalPath().equals(new File(path2).getCanonicalPath())) {
+                throw new RuntimeException(path1 + " and " + path2 + " are the same file: "
+                        + " refusing to process.");
+            }
+        }
+
         private void displayHelp() {
             MakedictLog.i("Usage: makedict "
-                    + "[-s <unigrams.xml> [-b <bigrams.xml>] | -s <binary input>] "
-                    + " [-d <binary output>] [-x <xml output>] [-2]\n"
+                    + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts.xml>] "
+                    + "| -s <binary input>] [-d <binary output format version 2>] "
+                    + "[-d1 <binary output format version 1>] [-x <xml output>] [-2]\n"
                     + "\n"
                     + "  Converts a source dictionary file to one or several outputs.\n"
                     + "  Source can be an XML file, with an optional XML bigrams file, or a\n"
                     + "  binary dictionary file.\n"
-                    + "  Both binary and XML outputs are supported. Both can be output at\n"
-                    + "  the same time but outputting several files of the same type is not\n"
-                    + "  supported.");
+                    + "  Binary version 1 (Ice Cream Sandwich), 2 (Jelly Bean) and XML outputs\n"
+                    + "  are supported. All three can be output at the same time, but the same\n"
+                    + "  output format cannot be specified several times. The behavior is\n"
+                    + "  unspecified if the same file is specified for input and output, or for\n"
+                    + "  several outputs.");
         }
 
-        public Arguments(String[] argsArray) {
+        public Arguments(String[] argsArray) throws IOException {
             final LinkedList<String> args = new LinkedList<String>(Arrays.asList(argsArray));
             if (args.isEmpty()) {
                 displayHelp();
             }
             String inputBinary = null;
             String inputUnigramXml = null;
+            String inputShortcutXml = null;
             String inputBigramXml = null;
             String outputBinary = null;
+            String outputBinaryFormat1 = null;
             String outputXml = null;
 
             while (!args.isEmpty()) {
@@ -105,7 +141,8 @@
                     } else {
                         // All these options need an argument
                         if (args.isEmpty()) {
-                            throw new RuntimeException("Option " + arg + " requires an argument");
+                            throw new IllegalArgumentException("Option " + arg + " is unknown or "
+                                    + "requires an argument");
                         }
                         String filename = args.get(0);
                         args.remove(0);
@@ -115,12 +152,18 @@
                             } else {
                                 inputUnigramXml = filename;
                             }
+                        } else if (OPTION_INPUT_SHORTCUT_XML.equals(arg)) {
+                            inputShortcutXml = filename;
                         } else if (OPTION_INPUT_BIGRAM_XML.equals(arg)) {
                             inputBigramXml = filename;
                         } else if (OPTION_OUTPUT_BINARY.equals(arg)) {
                             outputBinary = filename;
+                        } else if (OPTION_OUTPUT_BINARY_FORMAT_VERSION_1.equals(arg)) {
+                            outputBinaryFormat1 = filename;
                         } else if (OPTION_OUTPUT_XML.equals(arg)) {
                             outputXml = filename;
+                        } else {
+                            throw new IllegalArgumentException("Unknown option : " + arg);
                         }
                     }
                 } else {
@@ -133,15 +176,17 @@
                     } else if (null == outputBinary) {
                         outputBinary = arg;
                     } else {
-                        throw new RuntimeException("Several output binary files specified");
+                        throw new IllegalArgumentException("Several output binary files specified");
                     }
                 }
             }
 
             mInputBinary = inputBinary;
             mInputUnigramXml = inputUnigramXml;
+            mInputShortcutXml = inputShortcutXml;
             mInputBigramXml = inputBigramXml;
             mOutputBinary = outputBinary;
+            mOutputBinaryFormat1 = outputBinaryFormat1;
             mOutputXml = outputXml;
             checkIntegrity();
         }
@@ -167,7 +212,7 @@
         if (null != args.mInputBinary) {
             return readBinaryFile(args.mInputBinary);
         } else if (null != args.mInputUnigramXml) {
-            return readXmlFile(args.mInputUnigramXml, args.mInputBigramXml);
+            return readXmlFile(args.mInputUnigramXml, args.mInputShortcutXml, args.mInputBigramXml);
         } else {
             throw new RuntimeException("No input file specified");
         }
@@ -192,6 +237,7 @@
      * Read a dictionary from a unigram XML file, and optionally a bigram XML file.
      *
      * @param unigramXmlFilename the name of the unigram XML file. May not be null.
+     * @param shortcutXmlFilename the name of the shortcut XML file, or null if there is none.
      * @param bigramXmlFilename the name of the bigram XML file. Pass null if there are no bigrams.
      * @return the read dictionary.
      * @throws FileNotFoundException if one of the files can't be found
@@ -200,12 +246,14 @@
      * @throws ParserConfigurationException if the system can't create a SAX parser
      */
     private static FusionDictionary readXmlFile(final String unigramXmlFilename,
-            final String bigramXmlFilename) throws FileNotFoundException, SAXException,
-            IOException, ParserConfigurationException {
+            final String shortcutXmlFilename, final String bigramXmlFilename)
+            throws FileNotFoundException, SAXException, IOException, ParserConfigurationException {
         final FileInputStream unigrams = new FileInputStream(new File(unigramXmlFilename));
+        final FileInputStream shortcuts = null == shortcutXmlFilename ? null :
+                new FileInputStream(new File(shortcutXmlFilename));
         final FileInputStream bigrams = null == bigramXmlFilename ? null :
                 new FileInputStream(new File(bigramXmlFilename));
-        return XmlDictInputOutput.readDictionaryXml(unigrams, bigrams);
+        return XmlDictInputOutput.readDictionaryXml(unigrams, shortcuts, bigrams);
     }
 
     /**
@@ -218,9 +266,13 @@
      * @throws IOException if one of the output files can't be written to.
      */
     private static void writeOutputToParsedArgs(final Arguments args, final FusionDictionary dict)
-            throws FileNotFoundException, IOException {
+            throws FileNotFoundException, IOException, UnsupportedFormatException,
+            IllegalArgumentException {
         if (null != args.mOutputBinary) {
-            writeBinaryDictionary(args.mOutputBinary, dict);
+            writeBinaryDictionary(args.mOutputBinary, dict, 2);
+        }
+        if (null != args.mOutputBinaryFormat1) {
+            writeBinaryDictionary(args.mOutputBinaryFormat1, dict, 1);
         }
         if (null != args.mOutputXml) {
             writeXmlDictionary(args.mOutputXml, dict);
@@ -232,13 +284,16 @@
      *
      * @param outputFilename the name of the file to write to.
      * @param dict the dictionary to write.
+     * @param version the binary format version to use.
      * @throws FileNotFoundException if the output file can't be created.
      * @throws IOException if the output file can't be written to.
      */
     private static void writeBinaryDictionary(final String outputFilename,
-            final FusionDictionary dict) throws FileNotFoundException, IOException {
+            final FusionDictionary dict, final int version)
+            throws FileNotFoundException, IOException, UnsupportedFormatException {
         final File outputFile = new File(outputFilename);
-        BinaryDictInputOutput.writeDictionaryBinary(new FileOutputStream(outputFilename), dict);
+        BinaryDictInputOutput.writeDictionaryBinary(new FileOutputStream(outputFilename), dict,
+                version);
     }
 
     /**
diff --git a/tools/makedict/src/com/android/inputmethod/latin/makedict/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/makedict/XmlDictInputOutput.java
new file mode 100644
index 0000000..1d45fd2
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/makedict/XmlDictInputOutput.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.TreeSet;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Reads and writes XML files for a FusionDictionary.
+ *
+ * All functions in this class are static.
+ */
+public class XmlDictInputOutput {
+
+    private static final String ROOT_TAG = "wordlist";
+    private static final String WORD_TAG = "w";
+    private static final String BIGRAM_TAG = "bigram";
+    private static final String SHORTCUT_TAG = "shortcut";
+    private static final String FREQUENCY_ATTR = "f";
+    private static final String WORD_ATTR = "word";
+    private static final String SHORTCUT_ONLY_ATTR = "shortcutOnly";
+
+    private static final int SHORTCUT_ONLY_DEFAULT_FREQ = 1;
+
+    /**
+     * SAX handler for a unigram XML file.
+     */
+    static private class UnigramHandler extends DefaultHandler {
+        // Parser states
+        private static final int NONE = 0;
+        private static final int START = 1;
+        private static final int WORD = 2;
+        private static final int BIGRAM = 4;
+        private static final int END = 5;
+        private static final int UNKNOWN = 6;
+
+        final FusionDictionary mDictionary;
+        int mState; // the state of the parser
+        int mFreq; // the currently read freq
+        String mWord; // the current word
+        final HashMap<String, ArrayList<WeightedString>> mShortcutsMap;
+        final HashMap<String, ArrayList<WeightedString>> mBigramsMap;
+
+        /**
+         * Create the handler.
+         *
+         * @param dict the dictionary to construct.
+         * @param bigrams the bigrams as a map. This may be empty, but may not be null.
+         */
+        public UnigramHandler(final FusionDictionary dict,
+                final HashMap<String, ArrayList<WeightedString>> shortcuts,
+                final HashMap<String, ArrayList<WeightedString>> bigrams) {
+            mDictionary = dict;
+            mShortcutsMap = shortcuts;
+            mBigramsMap = bigrams;
+            mWord = "";
+            mState = START;
+            mFreq = 0;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attrs) {
+            if (WORD_TAG.equals(localName)) {
+                mState = WORD;
+                mWord = "";
+                for (int attrIndex = 0; attrIndex < attrs.getLength(); ++attrIndex) {
+                    final String attrName = attrs.getLocalName(attrIndex);
+                    if (FREQUENCY_ATTR.equals(attrName)) {
+                        mFreq = Integer.parseInt(attrs.getValue(attrIndex));
+                    }
+                }
+            } else if (ROOT_TAG.equals(localName)) {
+                for (int attrIndex = 0; attrIndex < attrs.getLength(); ++attrIndex) {
+                    final String attrName = attrs.getLocalName(attrIndex);
+                    mDictionary.mOptions.mAttributes.put(attrName, attrs.getValue(attrIndex));
+                }
+            } else {
+                mState = UNKNOWN;
+            }
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) {
+            if (WORD == mState) {
+                // The XML parser is free to return text in arbitrary chunks one after the
+                // other. In particular, this happens in some implementations when it finds
+                // an escape code like "&amp;".
+                mWord += String.copyValueOf(ch, start, length);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) {
+            if (WORD == mState) {
+                mDictionary.add(mWord, mFreq, mShortcutsMap.get(mWord), mBigramsMap.get(mWord));
+                mState = START;
+            }
+        }
+    }
+
+    static private class AssociativeListHandler extends DefaultHandler {
+        private final String SRC_TAG;
+        private final String SRC_ATTRIBUTE;
+        private final String DST_TAG;
+        private final String DST_ATTRIBUTE;
+        private final String DST_FREQ;
+
+        // In this version of the XML file, the bigram frequency is given as an int 0..XML_MAX
+        private final static int XML_MAX = 256;
+        // In memory and in the binary dictionary the bigram frequency is 0..MEMORY_MAX
+        private final static int MEMORY_MAX = 16;
+        private final static int XML_TO_MEMORY_RATIO = XML_MAX / MEMORY_MAX;
+
+        private String mSrc;
+        private final HashMap<String, ArrayList<WeightedString>> mAssocMap;
+
+        public AssociativeListHandler(final String srcTag, final String srcAttribute,
+                final String dstTag, final String dstAttribute, final String dstFreq) {
+            SRC_TAG = srcTag;
+            SRC_ATTRIBUTE = srcAttribute;
+            DST_TAG = dstTag;
+            DST_ATTRIBUTE = dstAttribute;
+            DST_FREQ = dstFreq;
+            mSrc = null;
+            mAssocMap = new HashMap<String, ArrayList<WeightedString>>();
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attrs) {
+            if (SRC_TAG.equals(localName)) {
+                mSrc = attrs.getValue(uri, SRC_ATTRIBUTE);
+            } else if (DST_TAG.equals(localName)) {
+                String dst = attrs.getValue(uri, DST_ATTRIBUTE);
+                int freq = Integer.parseInt(attrs.getValue(uri, DST_FREQ));
+                WeightedString bigram = new WeightedString(dst, freq / XML_TO_MEMORY_RATIO);
+                ArrayList<WeightedString> bigramList = mAssocMap.get(mSrc);
+                if (null == bigramList) bigramList = new ArrayList<WeightedString>();
+                bigramList.add(bigram);
+                mAssocMap.put(mSrc, bigramList);
+            }
+        }
+
+        public HashMap<String, ArrayList<WeightedString>> getAssocMap() {
+            return mAssocMap;
+        }
+    }
+
+    /**
+     * SAX handler for a bigram XML file.
+     */
+    static private class BigramHandler extends AssociativeListHandler {
+        private final static String BIGRAM_W1_TAG = "bi";
+        private final static String BIGRAM_W2_TAG = "w";
+        private final static String BIGRAM_W1_ATTRIBUTE = "w1";
+        private final static String BIGRAM_W2_ATTRIBUTE = "w2";
+        private final static String BIGRAM_FREQ_ATTRIBUTE = "p";
+
+        public BigramHandler() {
+            super(BIGRAM_W1_TAG, BIGRAM_W1_ATTRIBUTE, BIGRAM_W2_TAG, BIGRAM_W2_ATTRIBUTE,
+                    BIGRAM_FREQ_ATTRIBUTE);
+        }
+
+        public HashMap<String, ArrayList<WeightedString>> getBigramMap() {
+            return getAssocMap();
+        }
+    }
+
+    /**
+     * SAX handler for a shortcut XML file.
+     */
+    static private class ShortcutHandler extends AssociativeListHandler {
+        private final static String ENTRY_TAG = "entry";
+        private final static String ENTRY_ATTRIBUTE = "shortcut";
+        private final static String TARGET_TAG = "target";
+        private final static String REPLACEMENT_ATTRIBUTE = "replacement";
+        private final static String TARGET_PRIORITY_ATTRIBUTE = "priority";
+
+        public ShortcutHandler() {
+            super(ENTRY_TAG, ENTRY_ATTRIBUTE, TARGET_TAG, REPLACEMENT_ATTRIBUTE,
+                    TARGET_PRIORITY_ATTRIBUTE);
+        }
+
+        public HashMap<String, ArrayList<WeightedString>> getShortcutMap() {
+            return getAssocMap();
+        }
+    }
+
+    /**
+     * Reads a dictionary from an XML file.
+     *
+     * This is the public method that will parse an XML file and return the corresponding memory
+     * representation.
+     *
+     * @param unigrams the file to read the data from.
+     * @param shortcuts the file to read the shortcuts from, or null.
+     * @param bigrams the file to read the bigrams from, or null.
+     * @return the in-memory representation of the dictionary.
+     */
+    public static FusionDictionary readDictionaryXml(final InputStream unigrams,
+            final InputStream shortcuts, final InputStream bigrams)
+            throws SAXException, IOException, ParserConfigurationException {
+        final SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setNamespaceAware(true);
+        final SAXParser parser = factory.newSAXParser();
+        final BigramHandler bigramHandler = new BigramHandler();
+        if (null != bigrams) parser.parse(bigrams, bigramHandler);
+
+        final ShortcutHandler shortcutHandler = new ShortcutHandler();
+        if (null != shortcuts) parser.parse(shortcuts, shortcutHandler);
+
+        final FusionDictionary dict = new FusionDictionary();
+        final UnigramHandler unigramHandler =
+                new UnigramHandler(dict, shortcutHandler.getShortcutMap(),
+                        bigramHandler.getBigramMap());
+        parser.parse(unigrams, unigramHandler);
+
+        final HashMap<String, ArrayList<WeightedString>> shortcutMap =
+                shortcutHandler.getShortcutMap();
+        for (final String shortcut : shortcutMap.keySet()) {
+            if (dict.hasWord(shortcut)) continue;
+            // TODO: list a frequency in the shortcut file and use it here, instead of
+            // a constant freq
+            dict.addShortcutOnly(shortcut, SHORTCUT_ONLY_DEFAULT_FREQ, shortcutMap.get(shortcut));
+        }
+        return dict;
+    }
+
+    /**
+     * Reads a dictionary in the first, legacy XML format
+     *
+     * This method reads data from the parser and creates a new FusionDictionary with it.
+     * The format parsed by this method is the format used before Ice Cream Sandwich,
+     * which has no support for bigrams or shortcuts.
+     * It is important to note that this method expects the parser to have already eaten
+     * the first, all-encompassing tag.
+     *
+     * @param xpp the parser to read the data from.
+     * @return the parsed dictionary.
+     */
+
+    /**
+     * Writes a dictionary to an XML file.
+     *
+     * The output format is the "second" format, which supports bigrams and shortcuts.
+     *
+     * @param destination a destination stream to write to.
+     * @param dict the dictionary to write.
+     */
+    public static void writeDictionaryXml(Writer destination, FusionDictionary dict)
+            throws IOException {
+        final TreeSet<Word> set = new TreeSet<Word>();
+        for (Word word : dict) {
+            set.add(word);
+        }
+        // TODO: use an XMLSerializer if this gets big
+        destination.write("<wordlist format=\"2\"");
+        final HashMap<String, String> options = dict.mOptions.mAttributes;
+        for (final String key : dict.mOptions.mAttributes.keySet()) {
+            final String value = dict.mOptions.mAttributes.get(key);
+            destination.write(" " + key + "=\"" + value + "\"");
+        }
+        destination.write(">\n");
+        destination.write("<!-- Warning: there is no code to read this format yet. -->\n");
+        for (Word word : set) {
+            destination.write("  <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" "
+                    + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\" " + SHORTCUT_ONLY_ATTR
+                    + "=\"" + word.mIsShortcutOnly + "\">");
+            if (null != word.mShortcutTargets) {
+                destination.write("\n");
+                for (WeightedString target : word.mShortcutTargets) {
+                    destination.write("    <" + SHORTCUT_TAG + " " + FREQUENCY_ATTR + "=\""
+                            + target.mFrequency + "\">" + target.mWord + "</" + SHORTCUT_TAG
+                            + ">\n");
+                }
+                destination.write("  ");
+            }
+            if (null != word.mBigrams) {
+                destination.write("\n");
+                for (WeightedString bigram : word.mBigrams) {
+                    destination.write("    <" + BIGRAM_TAG + " " + FREQUENCY_ATTR + "=\""
+                            + bigram.mFrequency + "\">" + bigram.mWord + "</" + BIGRAM_TAG + ">\n");
+                }
+                destination.write("  ");
+            }
+            destination.write("</" + WORD_TAG + ">\n");
+        }
+        destination.write("</wordlist>\n");
+        destination.close();
+    }
+}
diff --git a/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java b/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
index 79cf14b..9682c9b 100644
--- a/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
+++ b/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.makedict;
 
-import com.android.inputmethod.latin.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 
 import java.util.ArrayList;
 
@@ -39,11 +39,11 @@
     // that it does not contain any duplicates.
     public void testFlattenNodes() {
         final FusionDictionary dict = new FusionDictionary();
-        dict.add("foo", 1, null);
-        dict.add("fta", 1, null);
-        dict.add("ftb", 1, null);
-        dict.add("bar", 1, null);
-        dict.add("fool", 1, null);
+        dict.add("foo", 1, null, null);
+        dict.add("fta", 1, null, null);
+        dict.add("ftb", 1, null, null);
+        dict.add("bar", 1, null, null);
+        dict.add("fool", 1, null, null);
         final ArrayList<Node> result = BinaryDictInputOutput.flattenTree(dict.mRoot);
         assertEquals(4, result.size());
         while (!result.isEmpty()) {