am f87df70c: (-s ours) am b9230840: Fix centering more suggestions panel on tablet (DO NOT MERGE)

* commit 'f87df70c10f497ff0497508e4e18d82372447f04':
  Fix centering more suggestions panel on tablet (DO NOT MERGE)
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 e9fa52e..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,11 +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..e663c90 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" />
diff --git a/java/proguard.flags b/java/proguard.flags
index 33af890..a5a6679 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -35,6 +35,12 @@
   *;
 }
 
--keep class com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder$MiniKeyboardParams {
+-keep class com.android.inputmethod.keyboard.MoreKeysKeyboard$Builder$MoreKeysKeyboardParams {
   <init>(...);
 }
+
+# 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/values-de-rZZ/donottranslate-more-keys.xml b/java/res/anim/alt_code_key_while_typing_fadein.xml
similarity index 71%
copy from java/res/values-de-rZZ/donottranslate-more-keys.xml
copy to java/res/anim/alt_code_key_while_typing_fadein.xml
index e7ec5e1..3f5fd5d 100644
--- a/java/res/values-de-rZZ/donottranslate-more-keys.xml
+++ b/java/res/anim/alt_code_key_while_typing_fadein.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,10 @@
 ** 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>
+
+<animator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:valueType="intType"
+    android:duration="100"
+    android:valueFrom="128"
+    android:valueTo="255" />
diff --git a/java/res/values-de-rZZ/donottranslate-more-keys.xml b/java/res/anim/alt_code_key_while_typing_fadeout.xml
similarity index 71%
copy from java/res/values-de-rZZ/donottranslate-more-keys.xml
copy to java/res/anim/alt_code_key_while_typing_fadeout.xml
index e7ec5e1..ed4a6f2 100644
--- a/java/res/values-de-rZZ/donottranslate-more-keys.xml
+++ b/java/res/anim/alt_code_key_while_typing_fadeout.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,10 @@
 ** 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>
+
+<animator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    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 70%
copy from java/res/anim/mini_keyboard_fadeout.xml
copy to java/res/anim/language_on_spacebar_fadeout.xml
index 535b100..f66e162 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,10 @@
 */
 -->
 
-<set
+<animator
     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:valueType="intType"
+    android:startOffset="1200"
+    android:duration="200"
+    android:valueFrom="255"
+    android:valueTo="128" />
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/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-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-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/background_voice.xml b/java/res/drawable/background_voice.xml
index 3b6137d..49a4191 100644
--- a/java/res/drawable/background_voice.xml
+++ b/java/res/drawable/background_voice.xml
@@ -22,4 +22,4 @@
         android:startColor="#ff000000"
         android:endColor="#ff000e29"
         android:angle="90" />
-</shape>
\ No newline at end of file
+</shape>
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/input_view.xml b/java/res/layout/input_view.xml
index 2e0cddc..0a8d71f 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -20,15 +20,15 @@
 
 <com.android.inputmethod.latin.InputView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
 >
+    <!-- The height of key_preview_backing view will automatically be determined by code. -->
     <View
         android:id="@+id/key_preview_backing"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/key_preview_backing_height" />
+        android:layout_height="0dip" />
 
     <!-- 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/mini_keyboard.xml b/java/res/layout/more_keys_keyboard.xml
similarity index 87%
rename from java/res/layout/mini_keyboard.xml
rename to java/res/layout/more_keys_keyboard.xml
index f7cd75c..6b2464b 100644
--- a/java/res/layout/mini_keyboard.xml
+++ b/java/res/layout/more_keys_keyboard.xml
@@ -22,11 +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: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 c49f958..49a00c6 100644
--- a/java/res/layout/more_suggestions.xml
+++ b/java/res/layout/more_suggestions.xml
@@ -22,9 +22,9 @@
         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_width="wrap_content"
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 36c9d4b..b1f7658 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,26 +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="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_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>
@@ -144,10 +137,9 @@
     <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>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Vibrasie-tydsduur met sleuteldruk"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volume met sleuteldruk"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Sleuteldruk se vibrasie-tydsduurinstellings"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Sleuteldruk se klankvolume-instellings"</string>
 </resources>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index d5280bc..bb0c493 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,26 +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="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_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>
@@ -144,7 +137,6 @@
     <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%
copy from java/res/values-fr-rCH/donottranslate-more-keys.xml
copy 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..46c9d8a 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,26 +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="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_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>
@@ -144,7 +137,6 @@
     <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-fr-rCH/donottranslate-more-keys.xml b/java/res/values-be/donottranslate-more-keys.xml
similarity index 74%
copy from java/res/values-fr-rCH/donottranslate-more-keys.xml
copy to java/res/values-be/donottranslate-more-keys.xml
index 561c5e5..1550ddb 100644
--- a/java/res/values-fr-rCH/donottranslate-more-keys.xml
+++ b/java/res/values-be/donottranslate-more-keys.xml
@@ -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>
+    <!-- U+045E: "ў" CYRILLIC SMALL LETTER SHORT U -->
+    <string name="keylabel_for_cyrillic_shcha">&#x045E;</string>
+    <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+    <string name="keylabel_for_cyrillic_i">&#x0456;</string>
 </resources>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 1ed944c..ac4433f 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..f3b9347 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..619dc38 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..6305f6e 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..739ab09 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_scandinavia_row1_11">&#x00E5;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="keylabel_for_scandinavia_row2_10">&#x00E6;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="keylabel_for_scandinavia_row2_11">&#x00F8;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="more_keys_for_scandinavia_row2_10">&#x00E4;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="more_keys_for_scandinavia_row2_11">&#x00F6;</string>
 </resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index b91acaa..df5dfa5 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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/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..a2584bc 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..1d6fc10 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..53d0252 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,26 +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="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_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>
@@ -144,7 +137,6 @@
     <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/additional-proximitychars.xml b/java/res/values-en/additional-proximitychars.xml
new file mode 100644
index 0000000..a5ff4a9
--- /dev/null
+++ b/java/res/values-en/additional-proximitychars.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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string-array name="additional_proximitychars">
+        <!-- Empty entry terminates the proximity chars array. -->
+
+        <!-- Additional proximity chars for a -->
+        <item>a</item>
+        <item>e</item>
+        <item>i</item>
+        <item>o</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for e -->
+        <item>e</item>
+        <item>a</item>
+        <item>i</item>
+        <item>o</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for i -->
+        <item>i</item>
+        <item>a</item>
+        <item>e</item>
+        <item>o</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for o -->
+        <item>o</item>
+        <item>a</item>
+        <item>e</item>
+        <item>i</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for u -->
+        <item>u</item>
+        <item>a</item>
+        <item>e</item>
+        <item>i</item>
+        <item>o</item>
+        <item></item>
+    </string-array>
+
+</resources>
diff --git a/java/res/values-en/donottranslate-more-keys.xml b/java/res/values-en/donottranslate-more-keys.xml
index bc26c6a..9073d3b 100644
--- a/java/res/values-en/donottranslate-more-keys.xml
+++ b/java/res/values-en/donottranslate-more-keys.xml
@@ -19,11 +19,11 @@
 -->
 <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_e">è,é,ê,ë,ē</string>
+    <string name="more_keys_for_i">î,ï,í,ī,ì</string>
+    <string name="more_keys_for_o">ô,ö,ò,ó,œ,ø,ō,õ</string>
     <string name="more_keys_for_s">ß</string>
-    <string name="more_keys_for_u">7,û,ü,ù,ú,ū</string>
+    <string name="more_keys_for_u">û,ü,ù,ú,ū</string>
     <string name="more_keys_for_n">ñ</string>
     <string name="more_keys_for_c">ç</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 95e309f..e111bdd 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,13 +36,19 @@
     <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="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Demora en rechazo de ventana emergente de clave"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opciones para expertos"</string>
+    <!-- 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 />
+    <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>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nombres de contacto"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Usar nombres de los contactos para sugerencias y correcciones"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Habilitar correcciones"</string>
+    <string name="enable_span_insert" msgid="7204653105667167620">"Activar correcciones"</string>
     <string name="enable_span_insert_summary" msgid="2947317657871394467">"Establecer sugerencias para realizar correcciones"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionarios complementarios"</string>
@@ -50,9 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar en modo 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 puntuación insertan automáticamente las palabras corregidas"</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>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
@@ -64,6 +71,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 +84,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,26 +96,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="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_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>
@@ -128,7 +124,7 @@
     <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="voice_input" msgid="3583258583521397548">"Clave de entrada de voz"</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>
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Desactivado"</string>
@@ -141,10 +137,9 @@
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tocar de nuevo para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionario disponible"</string>
-    <string name="prefs_enable_log" msgid="6620424505072963557">"Habilitar los comentarios del usuario"</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..2018c55 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..be66b42
--- /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_scandinavia_row1_11">&#x00FC;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_scandinavia_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_scandinavia_row2_11">&#x00E4;</string>
+    <!-- U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
+    <string name="more_keys_for_scandinavia_row2_10">&#x00F5;</string>
+</resources>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 2c69c99..c8e98a1 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..3e13c79
--- /dev/null
+++ b/java/res/values-fa/donottranslate-more-keys.xml
@@ -0,0 +1,160 @@
+<?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+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>
+    <!-- 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">&#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..2696ed6 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,26 +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="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_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>
@@ -148,7 +141,6 @@
     <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..9b5fa78 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_scandinavia_row1_11">&#x00E5;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_scandinavia_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_scandinavia_row2_11">&#x00E4;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_scandinavia_row2_10">&#x00F8;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="more_keys_for_scandinavia_row2_11">&#x00E6;</string>
 </resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index c77cd81..ecb7a4d 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -123,7 +119,7 @@
     <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">"Äänihakua ei asennettu"</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>
@@ -144,7 +140,6 @@
     <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-fr-rCA/donottranslate-more-keys.xml b/java/res/values-fr-rCA/donottranslate-more-keys.xml
deleted file mode 100644
index 80e9d93..0000000
--- a/java/res/values-fr-rCA/donottranslate-more-keys.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.
-*/
--->
-<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>
-</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..3a5f6a1 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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/strings.xml b/java/res/values-hi/strings.xml
index de95ab8..c20e50f 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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 30c20b3..ef491f4 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,8 +36,14 @@
     <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="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Bez odgode klj. skočnih"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Opcije za stručnjake"</string>
+    <!-- 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 />
+    <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>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Predlaži imena kontakata"</string>
@@ -50,9 +58,8 @@
     <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">"Razm. i intrp. aut. ispr. kr. rči."</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>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Skromno"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivno"</string>
@@ -64,6 +71,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 +84,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,26 +96,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="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_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>
@@ -132,7 +128,7 @@
     <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>
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Isključeno"</string>
-    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mik. na gl. tipk."</string>
+    <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>
@@ -144,7 +140,6 @@
     <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..4ac6bf3 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..f10d613 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,16 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -141,10 +137,9 @@
     <string name="select_language" msgid="3693815588777926848">"Bahasa masukan"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Sentuh sekali lagi untuk menyimpan"</string>
     <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-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..d65a568 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..dabbd4d 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,26 +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="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_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>
@@ -144,7 +137,6 @@
     <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..052c4c8 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..8ec334d 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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-fr-rCH/donottranslate-more-keys.xml b/java/res/values-ky/donottranslate-more-keys.xml
similarity index 66%
copy from java/res/values-fr-rCH/donottranslate-more-keys.xml
copy to java/res/values-ky/donottranslate-more-keys.xml
index 561c5e5..b67a9f1 100644
--- a/java/res/values-fr-rCH/donottranslate-more-keys.xml
+++ b/java/res/values-ky/donottranslate-more-keys.xml
@@ -18,9 +18,10 @@
 */
 -->
 <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>
+    <!-- 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+04E9: "ө" CYRILLIC SMALL LETTER BARRED O -->
+    <string name="more_keys_for_cyrillic_o">&#x04E9;</string>
 </resources>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 4a5c5a4..73192d9 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -53,19 +53,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>
+    <fraction name="spacebar_text_ratio">40.000%</fraction>
     <dimen name="key_preview_offset">0.08in</dimen>
 
     <dimen name="key_preview_offset_ics">0.01in</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-0.140in</dimen>
 
     <dimen name="suggestions_strip_height">36dip</dimen>
     <dimen name="more_suggestions_row_height">36dip</dimen>
     <integer name="max_more_suggestions_row">2</integer>
     <fraction name="min_more_suggestions_width">60%</fraction>
-    <!-- key_preview_backing_height = more_suggestions_row_height * max_more_suggestions_row -->
-    <dimen name="key_preview_backing_height">72dip</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.336in</dimen>
+    <dimen name="more_keys_keyboard_slide_allowance">0.336in</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-0.280in</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-0.280in</dimen>
 </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..09b017a 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..d358a84 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..e96a306
--- /dev/null
+++ b/java/res/values-mk/donottranslate-more-keys.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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- NOTE: Macedonian layouts are based on Serbian ones with the following key replacements. -->
+    <!-- U+0455: "ѕ" CYRILLIC SMALL LETTER DZE -->
+    <string name="keylabel_for_cyrillic_ze">&#x0455;</string>
+    <!-- U+045C: "ќ" CYRILLIC SMALL LETTER KJE -->
+    <string name="keylabel_for_cyrillic_tshe">&#x045C;</string>
+    <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE -->
+    <string name="keylabel_for_cyrillic_dze">&#x0437;</string>
+    <!-- U+0453: "ѓ" CYRILLIC SMALL LETTER GJE -->
+    <string name="keylabel_for_cyrillic_dje">&#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..246c4d7 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 ptukaran bhs"</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,26 +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="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_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>
@@ -144,7 +137,6 @@
     <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..7248d98 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_scandinavia_row1_11">&#x00E5;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="keylabel_for_scandinavia_row2_10">&#x00F8;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="keylabel_for_scandinavia_row2_11">&#x00E6;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="more_keys_for_scandinavia_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="more_keys_for_scandinavia_row2_11">&#x00E4;</string>
 </resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index acd636b..8114cd5 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +69,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..4f5cbee 100644
--- a/java/res/values-nl/donottranslate-more-keys.xml
+++ b/java/res/values-nl/donottranslate-more-keys.xml
@@ -18,10 +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_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 -->
+    <string name="more_keys_for_i">&#x00ED;,&#x00EF;,&#x00EC;,&#x00EE;,&#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+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-nl/strings.xml b/java/res/values-nl/strings.xml
index 7f0ff7e..56d423a 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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 a9a6a76..ec9e014 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,29 +96,17 @@
     <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="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_warning_title" msgid="4419354150908395008">"Wprowadzanie głosowe"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Wprowadzanie głosowe obecnie nie jest obsługiwane w Twoim języku, ale działa w języku angielskim."</string>
-    <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Funkcja wprowadzania głosowego wykorzystuje mechanizm rozpoznawania mowy. Obowiązuje "<a href="http://m.google.com/privacy">"Polityka prywatności Google Mobile"</a>"."</string>
+    <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>
@@ -144,7 +140,6 @@
     <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..4e0cf4c 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..b3af6f0 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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 090f3fc..8fcb67d 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,10 +84,10 @@
     <skip />
     <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
     <skip />
-    <!-- no translation found for prefs_settings_key (4623341240804046498) -->
+    <!-- no translation found for auto_correction (4979925752001319458) -->
     <skip />
-    <!-- outdated translation 7911639788808958255 -->     <string name="auto_correction" msgid="4979925752001319458">"Propostas da pleds"</string>
-    <!-- outdated translation 6881047311475758267 -->     <string name="auto_correction_summary" msgid="5625751551134658006">"Curreger automaticamain il pled precedent"</string>
+    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
+    <skip />
     <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
     <skip />
     <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
@@ -86,7 +96,8 @@
     <skip />
     <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
     <skip />
-    <!-- outdated translation 1323347224043514969 -->     <string name="bigram_suggestion" msgid="2636414079905220518">"Propostas da tip bigram"</string>
+    <!-- no translation found for bigram_suggestion (2636414079905220518) -->
+    <skip />
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Meglierar la proposta cun agid dal pled precedent"</string>
     <!-- no translation found for bigram_prediction (8914273444762259739) -->
     <skip />
@@ -95,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) -->
@@ -117,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 />
@@ -141,51 +154,30 @@
     <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) -->
-    <skip />
-    <!-- no translation found for spoken_description_ellipsis (1687670869947652062) -->
-    <skip />
-    <!-- no translation found for spoken_description_low_double_quote (3551394572784840975) -->
+    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
     <skip />
     <string name="voice_warning_title" msgid="4419354150908395008">"Cumonds vocals"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"\"Cumonds vocals en Vossa lingua na vegnan actualmain betg sustegnids, ma la funcziun è disponibla per englais.\""</string>
-    <!-- outdated translation 4611518823070986445 -->     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Ils cumonds vocals èn ina funcziunalitad experimentala che utilisescha la renconuschientscha vocala da rait da Google."</string>
-    <!-- outdated translation 5652369578498701761 -->     <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"\"Per deactivar ils cumonds vocals, avri ils parameters da tastatura.\""</string>
-    <!-- outdated translation 6892342981545727994 -->     <string name="voice_hint_dialog_message" msgid="1420686286820661548">"\"Per utilisar ils cumonds vocals, smatgai il buttun dal microfon u stritgai cun il det sur la tastatura dal visur.\""</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>
@@ -201,7 +193,8 @@
     <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>
-    <!-- outdated translation 2466640768843347841 -->     <string name="voice_input" msgid="3583258583521397548">"Cumonds vocals"</string>
+    <!-- no translation found for voice_input (3583258583521397548) -->
+    <skip />
     <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
     <skip />
     <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
@@ -221,12 +214,12 @@
     <string name="language_selection_title" msgid="1651299598555326750">"Linguas da cumonds vocals"</string>
     <!-- no translation found for select_language (3693815588777926848) -->
     <skip />
-    <!-- outdated translation 8058519710062071085 -->     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Tippar danovamain per memorisar"</string>
+    <!-- no translation found for hint_add_to_dictionary (9006292060636342317) -->
+    <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>
-    <!-- outdated translation 437433231038683666 -->     <string name="keyboard_layout" msgid="8451164783510487501">"Design da la tastatura"</string>
-    <!-- no translation found for subtype_de_qwerty (3358900499589259491) -->
+    <!-- no translation found for keyboard_layout (8451164783510487501) -->
     <skip />
     <!-- no translation found for subtype_en_GB (88170601942311355) -->
     <skip />
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..2445075 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..a1a22e5 100644
--- a/java/res/values-ru/donottranslate-more-keys.xml
+++ b/java/res/values-ru/donottranslate-more-keys.xml
@@ -18,7 +18,6 @@
 */
 -->
 <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+0451: "ё" CYRILLIC SMALL LETTER IO -->
+    <string name="more_keys_for_cyrillic_ye">&#x0451;</string>
 </resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 863c8a2..0d3d21d 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="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="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="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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,9 +140,8 @@
     <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="subtype_en_GB" msgid="88170601942311355">"английский (Великобритания)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"английский (США)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим проверки удобства использования"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Настройки вибросигнала при нажатии клавиш"</string>
     <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Настройки громкости звука при нажатии клавиш"</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..91896d0 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..88d29a9 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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/strings.xml b/java/res/values-sr/strings.xml
index 0906fce..463165b 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..e3662df 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_scandinavia_row1_11">&#x00E5;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_scandinavia_row2_10">&#x00F6;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_scandinavia_row2_11">&#x00E4;</string>
+    <!-- U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE -->
+    <string name="more_keys_for_scandinavia_row2_10">&#x00F8;</string>
+    <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
+    <string name="more_keys_for_scandinavia_row2_11">&#x00E6;</string>
 </resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 46760bb..992604a 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..12185ca 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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 1b8c8a6..68083b5 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -48,10 +48,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>
     <integer name="max_more_suggestions_row">5</integer>
     <fraction name="min_more_suggestions_width">50%</fraction>
-    <!-- key_preview_backing_height = more_suggestions_row_height * max_more_suggestions_row -->
-    <dimen name="key_preview_backing_height">220dip</dimen>
 </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 3183023..a861024 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -40,12 +40,12 @@
 
     <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">6dip</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">15.6mm</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-13.0mm</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-13.0mm</dimen>
 
     <!-- left or right padding of label alignment -->
     <dimen name="key_label_horizontal_padding">6dip</dimen>
@@ -59,18 +59,19 @@
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">26%</fraction>
     <fraction name="key_preview_text_ratio">50%</fraction>
+    <fraction name="spacebar_text_ratio">32.14%</fraction>
     <dimen name="key_preview_height">15.0mm</dimen>
     <dimen name="key_preview_offset">0.1in</dimen>
 
     <dimen name="key_preview_height_ics">15.0mm</dimen>
     <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-5mm</dimen>
 
     <dimen name="suggestions_strip_height">44dip</dimen>
     <dimen name="more_suggestions_row_height">44dip</dimen>
     <integer name="max_more_suggestions_row">6</integer>
     <fraction name="min_more_suggestions_width">90%</fraction>
-    <!-- key_preview_backing_height = more_suggestions_row_height * max_more_suggestions_row -->
-    <dimen name="key_preview_backing_height">264dip</dimen>
     <dimen name="suggestions_strip_padding">15.0mm</dimen>
     <dimen name="suggestion_min_width">0.3in</dimen>
     <dimen name="suggestion_padding">12dip</dimen>
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index 664e8c1..ef39f43 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -52,6 +52,7 @@
     <fraction name="key_hint_letter_ratio">23%</fraction>
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">24%</fraction>
+    <fraction name="spacebar_text_ratio">24.00%</fraction>
     <dimen name="key_preview_height">17.0mm</dimen>
 
     <dimen name="key_preview_height_ics">26.5mm</dimen>
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 bb4937d..a9ad030 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -43,12 +43,12 @@
 
     <dimen name="popup_key_height">10.0mm</dimen>
 
-    <dimen name="mini_keyboard_key_horizontal_padding">12dip</dimen>
+    <dimen name="more_keys_keyboard_key_horizontal_padding">12dip</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">15.6mm</dimen>
+    <dimen name="more_keys_keyboard_slide_allowance">15.6mm</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-13.0mm</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction">-13.0mm</dimen>
 
     <!-- left or right padding of label alignment -->
     <dimen name="key_label_horizontal_padding">6dip</dimen>
@@ -62,18 +62,19 @@
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">26%</fraction>
     <fraction name="key_preview_text_ratio">50%</fraction>
+    <fraction name="spacebar_text_ratio">29.03%</fraction>
     <dimen name="key_preview_height">15.0mm</dimen>
     <dimen name="key_preview_offset">0.1in</dimen>
 
     <dimen name="key_preview_height_ics">15.0mm</dimen>
     <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-5mm</dimen>
 
     <dimen name="suggestions_strip_height">44dip</dimen>
     <dimen name="more_suggestions_row_height">44dip</dimen>
     <integer name="max_more_suggestions_row">6</integer>
     <fraction name="min_more_suggestions_width">90%</fraction>
-    <!-- key_preview_backing_height = more_suggestions_row_height * max_more_suggestions_row -->
-    <dimen name="key_preview_backing_height">264dip</dimen>
     <dimen name="suggestions_strip_padding">15.0mm</dimen>
     <dimen name="suggestion_min_width">46dip</dimen>
     <dimen name="suggestion_padding">8dip</dimen>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 53f5660..f291fcc 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..53ae616 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..01acfbf 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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-fr-rCH/donottranslate-more-keys.xml b/java/res/values-uk/donottranslate-more-keys.xml
similarity index 74%
rename from java/res/values-fr-rCH/donottranslate-more-keys.xml
rename to java/res/values-uk/donottranslate-more-keys.xml
index 561c5e5..323eaaf 100644
--- a/java/res/values-fr-rCH/donottranslate-more-keys.xml
+++ b/java/res/values-uk/donottranslate-more-keys.xml
@@ -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>
+    <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+    <string name="keylabel_for_cyrillic_yery">&#x0456;</string>
+    <!-- U+0457: "ї" CYRILLIC SMALL LETTER YI -->
+    <string name="more_keys_for_cyrillic_yery">&#x0457;</string>
 </resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 234a9c5..ea970fe 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..25d1f28 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..ef42b7f 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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 8d60fb7..a2c2f02 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,13 @@
     <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>
+    <!-- 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 />
     <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 +58,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 +71,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 +84,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,26 +96,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="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_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>
@@ -144,7 +140,6 @@
     <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..3646a85 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 ilimi 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,26 +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="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_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>
@@ -144,7 +137,6 @@
     <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-de-rZZ/donottranslate-more-keys.xml b/java/res/values/additional-proximitychars.xml
similarity index 82%
rename from java/res/values-de-rZZ/donottranslate-more-keys.xml
rename to java/res/values/additional-proximitychars.xml
index e7ec5e1..03d10d5 100644
--- a/java/res/values-de-rZZ/donottranslate-more-keys.xml
+++ b/java/res/values/additional-proximitychars.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,6 +18,6 @@
 */
 -->
 <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>
+    <string-array name="additional_proximitychars">
+    </string-array>
 </resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 0c9ca4f..e619ad0 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,42 @@
         </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="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 +168,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" />
@@ -164,10 +207,6 @@
         <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 +217,26 @@
         <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" />
     </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 +244,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 +273,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 +304,22 @@
             <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" />
         </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 +344,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 +366,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 +384,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 +397,26 @@
         <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" />
+    <!-- TODO: Should be removed. This is no longer required if InputMethodSubtype is supported. -->
+    <declare-styleable name="KeyboardSet">
+        <!-- Locale of the keyboard layouts -->
+        <attr name="keyboardLocale" format="string" />
+    </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"/>
     </declare-styleable>
 </resources>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 3f676ab..c51800f 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,43 @@
     <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_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">0.05in</dimen>
+    <integer name="config_touch_noise_threshold_time">40</integer>
+    <dimen name="config_touch_noise_threshold_distance">2.0mm</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 +84,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"
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 352141c..37720ce 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -26,8 +26,8 @@
 
     <dimen name="popup_key_height">0.330in</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">16dip</dimen>
+    <dimen name="more_keys_keyboard_key_horizontal_padding">8dip</dimen>
 
     <fraction name="keyboard_top_padding">1.556%p</fraction>
     <fraction name="keyboard_bottom_padding">4.669%p</fraction>
@@ -48,13 +48,13 @@
     <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">4dip</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">0.396in</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-0.330in</dimen>
+    <dimen name="more_keys_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>
@@ -66,6 +66,7 @@
     <fraction name="key_hint_label_ratio">44%</fraction>
     <fraction name="key_uppercase_letter_ratio">35%</fraction>
     <fraction name="key_preview_text_ratio">82%</fraction>
+    <fraction name="spacebar_text_ratio">33.735%</fraction>
     <dimen name="key_preview_height">80sp</dimen>
     <dimen name="key_preview_offset">0.1in</dimen>
 
@@ -76,6 +77,8 @@
 
     <dimen name="key_preview_height_ics">80sp</dimen>
     <dimen name="key_preview_offset_ics">0.05in</dimen>
+    <!-- popup_key_height x -0.5 -->
+    <dimen name="more_keys_keyboard_vertical_correction_ics">-0.165in</dimen>
 
     <dimen name="suggestions_strip_height">40dip</dimen>
     <dimen name="more_suggestions_key_horizontal_padding">12dip</dimen>
@@ -86,8 +89,6 @@
     <integer name="max_more_suggestions_row">6</integer>
     <fraction name="min_more_suggestions_width">90%</fraction>
     <fraction name="more_suggestions_info_ratio">18%</fraction>
-    <!-- key_preview_backing_height = more_suggestions_row_height * max_more_suggestions_row -->
-    <dimen name="key_preview_backing_height">240dip</dimen>
     <dimen name="suggestions_strip_padding">0dip</dimen>
     <dimen name="suggestion_min_width">44dip</dimen>
     <dimen name="suggestion_padding">6dip</dimen>
@@ -95,6 +96,4 @@
     <dimen name="more_suggestions_hint_text_size">27dip</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/donottranslate-more-keys.xml b/java/res/values/donottranslate-more-keys.xml
index 6c77539..6577acd 100644
--- a/java/res/values/donottranslate-more-keys.xml
+++ b/java/res/values/donottranslate-more-keys.xml
@@ -19,43 +19,67 @@
 -->
 <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_row1_11"></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_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>
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <string name="keylabel_for_cyrillic_shcha">&#x0449;</string>
+    <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
+    <string name="keylabel_for_cyrillic_yery">&#x044B;</string>
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <string name="keylabel_for_cyrillic_i">&#x0438;</string>
+    <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE -->
+    <string name="keylabel_for_cyrillic_ze">&#x0437;</string>
+    <!-- U+045B: "ћ" CYRILLIC SMALL LETTER TSHE -->
+    <string name="keylabel_for_cyrillic_tshe">&#x045B;</string>
+    <!-- U+0455: "ѕ" CYRILLIC SMALL LETTER DZE -->
+    <string name="keylabel_for_cyrillic_dze">&#x0455;</string>
+    <!-- U+0452: "ђ" CYRILLIC SMALL LETTER DJE -->
+    <string name="keylabel_for_cyrillic_dje">&#x0452;</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>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_ha">&#x044A;</string>
+    <string name="more_keys_for_cyrillic_yery"></string>
+    <string name="more_keys_for_cyrillic_o"></string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</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 +90,115 @@
     <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_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..74c6846 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>
@@ -161,9 +166,12 @@
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
+    <!-- Description for generic QWERTY keyboard subtype -->
+    <string name="subtype_generic_qwerty">%s (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-icons-black.xml b/java/res/values/keyboard-icons-black.xml
index f767cb3..44fc2b9 100644
--- a/java/res/values/keyboard-icons-black.xml
+++ b/java/res/values/keyboard-icons-black.xml
@@ -30,10 +30,11 @@
         <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>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-ics.xml b/java/res/values/keyboard-icons-ics.xml
index f102143..5fba025 100644
--- a/java/res/values/keyboard-icons-ics.xml
+++ b/java/res/values/keyboard-icons-ics.xml
@@ -23,16 +23,16 @@
         <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>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-white.xml b/java/res/values/keyboard-icons-white.xml
index 07ece66..837b1a3 100644
--- a/java/res/values/keyboard-icons-white.xml
+++ b/java/res/values/keyboard-icons-white.xml
@@ -26,10 +26,11 @@
         <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>
     </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..f2c02d0 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,47 +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>
+
+    <!-- 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>
+
+    <!-- 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>
 
     <!-- Voice related labels -->
 
@@ -320,7 +307,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">&#x2190; 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 +320,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..7d8b7dd 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,14 +60,39 @@
         <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="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>
@@ -80,16 +100,16 @@
         <item name="horizontalGap">0dip</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 +118,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 +131,7 @@
     </style>
     <style
         name="MoreSuggestionsViewStyle"
-        parent="MiniKeyboardView"
+        parent="MoreKeysKeyboardView"
     >
     </style>
     <style name="SuggestionBackgroundStyle">
@@ -124,6 +145,7 @@
         name="Keyboard.HighContrast"
         parent="Keyboard"
     >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">1</item>
     </style>
     <style
@@ -133,11 +155,22 @@
         <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>
@@ -146,13 +179,6 @@
         <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,12 +187,22 @@
         <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"
+        name="LatinKeyboardView.Stone"
+        parent="KeyboardView.Stone"
+    >
+        <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="MoreKeysKeyboard.Stone"
         parent="Keyboard.Stone"
     >
         <item name="keyboardTopPadding">0dip</item>
@@ -174,8 +210,8 @@
         <item name="horizontalGap">0dip</item>
     </style>
     <style
-        name="MiniKeyboardView.Stone"
-        parent="MiniKeyboardView"
+        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,7 +261,17 @@
         <item name="keyTextStyle">bold</item>
     </style>
     <style
-        name="MiniKeyboard.Gingerbread"
+        name="LatinKeyboardView.Gingerbread"
+        parent="KeyboardView.Gingerbread"
+    >
+        <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.Gingerbread"
         parent="Keyboard.Gingerbread"
     >
         <item name="keyboardTopPadding">0dip</item>
@@ -221,8 +279,8 @@
         <item name="horizontalGap">0dip</item>
     </style>
     <style
-        name="MiniKeyboardView.Gingerbread"
-        parent="MiniKeyboardView"
+        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,8 +307,8 @@
         <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>
@@ -268,7 +321,17 @@
         <item name="shadowRadius">0.0</item>
     </style>
     <style
-        name="MiniKeyboard.IceCreamSandwich"
+        name="LatinKeyboardView.IceCreamSandwich"
+        parent="KeyboardView.IceCreamSandwich"
+    >
+        <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="MoreKeysKeyboard.IceCreamSandwich"
         parent="Keyboard.IceCreamSandwich"
     >
         <item name="keyboardTopPadding">0dip</item>
@@ -276,16 +339,17 @@
         <item name="horizontalGap">0dip</item>
     </style>
     <style
-        name="MiniKeyboardView.IceCreamSandwich"
-        parent="MiniKeyboardView"
+        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 +360,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 +375,7 @@
     </style>
     <style
         name="MoreSuggestionsViewStyle.IceCreamSandwich"
-        parent="MiniKeyboardView.IceCreamSandwich"
+        parent="MoreKeysKeyboardView.IceCreamSandwich"
     >
     </style>
     <style name="SuggestionBackgroundStyle.IceCreamSandwich">
@@ -318,11 +383,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/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_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..7b70f63
--- /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"
+    latin:keyboardLocale="ar" >
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_arabic" />
+    <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..8232346
--- /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"
+    latin:keyboardLocale="be">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic" />
+    <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..49914d5
--- /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"
+    latin:keyboardLocale="bg">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_bulgarian" />
+    <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..b453516
--- /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"
+    latin:keyboardLocale="cs">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz" />
+    <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..9b512ec
--- /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"
+    latin:keyboardLocale="da">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic" />
+    <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-rZZ/keyboard_set.xml b/java/res/xml-de-rZZ/keyboard_set.xml
new file mode 100644
index 0000000..635884d
--- /dev/null
+++ b/java/res/xml-de-rZZ/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"
+    latin:keyboardLocale="de">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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/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..485e63f
--- /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"
+    latin:keyboardLocale="de">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz" />
+    <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..19ecb77
--- /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"
+    latin:keyboardLocale="el">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_greek" />
+    <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..9cf208b
--- /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"
+    latin:keyboardLocale="es,es_US">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_spanish" />
+    <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..9ccef8c
--- /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"
+    latin:keyboardLocale="et">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic" />
+    <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..9bd1050
--- /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"
+    latin:keyboardLocale="fa" >
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_arabic" />
+    <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..45968af
--- /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"
+    latin:keyboardLocale="fi">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic" />
+    <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/keyboard_set.xml b/java/res/xml-fr-rCA/keyboard_set.xml
new file mode 100644
index 0000000..ea6ac8f
--- /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"
+    latin:keyboardLocale="fr_CA">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..751900b
--- /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"
+    latin:keyboardLocale="fr_CH">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz" />
+    <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..fa1339b
--- /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"
+    latin:keyboardLocale="fr">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_azerty" />
+    <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-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..e17aefd
--- /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"
+    latin:keyboardLocale="hr">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz" />
+    <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-hu/kbd_qwerty.xml
deleted file mode 100644
index 3195d5b..0000000
--- a/java/res/xml-hu/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="hu"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwertz" />
-</Keyboard>
diff --git a/java/res/xml-hu/keyboard_set.xml b/java/res/xml-hu/keyboard_set.xml
new file mode 100644
index 0000000..0f6e575
--- /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"
+    latin:keyboardLocale="hu">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwertz" />
+    <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..501ba96
--- /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"
+    latin:keyboardLocale="iw">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_hebrew" />
+    <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-ka/keyboard_set.xml b/java/res/xml-ka/keyboard_set.xml
new file mode 100644
index 0000000..1b0bc2d
--- /dev/null
+++ b/java/res/xml-ka/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"
+    latin:keyboardLocale="ka">
+    <Element
+        latin:elementName="alphabet"
+        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..71215c5
--- /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"
+    latin:keyboardLocale="ky">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic" />
+    <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..5eabe8c
--- /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"
+    latin:keyboardLocale="mk">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_south_slavic" />
+    <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..2d585a9
--- /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"
+    latin:keyboardLocale="nb">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic" />
+    <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..6d27379
--- /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"
+    latin:keyboardLocale="pl">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..65f9634
--- /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"
+    latin:keyboardLocale="pt">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..6c34966
--- /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"
+    latin:keyboardLocale="ro">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..76daf32
--- /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"
+    latin:keyboardLocale="ru">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic" />
+    <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..b283d96
--- /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"
+    latin:keyboardLocale="sk">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..dbb2782
--- /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"
+    latin:keyboardLocale="sl">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..71dedf4
--- /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"
+    latin:keyboardLocale="sr">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_south_slavic" />
+    <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..7135fc5
--- /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"
+    latin:keyboardLocale="sv">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_nordic" />
+    <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-ar/kbd_symbols.xml b/java/res/xml-sw600dp-land/kbd_number.xml
similarity index 85%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw600dp-land/kbd_number.xml
index 9e5c255..9d358b6 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml-sw600dp-land/kbd_phone.xml
similarity index 85%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw600dp-land/kbd_phone.xml
index 9e5c255..abac6bd 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml-sw600dp-land/kbd_phone_symbols.xml
similarity index 80%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw600dp-land/kbd_phone_symbols.xml
index 9e5c255..e3f56bc 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_azerty_symbols.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_azerty_symbols.xml
index 7bdfbad..66254de 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_azerty_symbols_shift.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_azerty_symbols_shift.xml
index 7bdfbad..3c5ed5e 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        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-ar/kbd_symbols.xml b/java/res/xml-sw600dp/kbd_phone_symbols.xml
similarity index 84%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw600dp/kbd_phone_symbols.xml
index 9e5c255..9faeaf4 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyWidth="15.00%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        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-fr-rCA/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_spanish_symbols.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_spanish_symbols.xml
index 7bdfbad..66254de 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml-sw600dp/kbd_spanish_symbols_shift.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml-sw600dp/kbd_spanish_symbols_shift.xml
index 7bdfbad..3c5ed5e 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_10_10_7_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml-sw600dp/key_azerty_quote.xml
similarity index 75%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml-sw600dp/key_azerty_quote.xml
index 7bdfbad..0e4a8ec 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</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%
rename from java/res/xml-sv/kbd_qwerty.xml
rename 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..8869ab9
--- /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"
+                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..a263acd
--- /dev/null
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -0,0 +1,161 @@
+<?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" />
+    <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:keyLabel="&#x200C;"
+        latin:moreKeys="&#x200D;"
+        latin:keyLabelFlags="hasPopupHint" />
+    <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_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_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/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_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_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-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-ar/kbd_symbols.xml b/java/res/xml-sw768dp-land/kbd_number.xml
similarity index 85%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw768dp-land/kbd_number.xml
index 9e5c255..3ad25a3 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_number" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml-sw768dp-land/kbd_phone.xml
similarity index 85%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw768dp-land/kbd_phone.xml
index 9e5c255..abe7e7c 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
similarity index 80%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml-sw768dp-land/kbd_phone_symbols.xml
index 9e5c255..641464d 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
+    <!-- Tablet doesn't have phone symbols keyboard -->
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_phone" />
 </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-fr-rCA/kbd_qwerty.xml b/java/res/xml-sw768dp/key_settings.xml
similarity index 68%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml-sw768dp/key_settings.xml
index 7bdfbad..0359a99 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</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..fade151
--- /dev/null
+++ b/java/res/xml-sw768dp/key_styles_common.xml
@@ -0,0 +1,160 @@
+<?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" />
+    <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:keyLabel="&#x200C;"
+        latin:moreKeys="&#x200D;"
+        latin:keyLabelFlags="hasPopupHint" />
+    <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 70%
rename from java/res/xml-sw768dp/kbd_qwerty_row4.xml
rename to java/res/xml-sw768dp/row_qwerty4.xml
index e35e47d..0aa04f8 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,47 @@
                     <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:keyLabel="&#x200C;"
+                    latin:moreKeys="&#x200D;"
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:altCode="@integer/key_space"
+                    latin:keyActionFlags="altCodeWhileTyping" />
+            </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/rows_10_10_7_symbols.xml b/java/res/xml-sw768dp/rows_10_10_7_symbols.xml
new file mode 100644
index 0000000..9901dec
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_10_10_7_symbols.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"
+>
+    <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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..b6aa202
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.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"
+>
+    <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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..d5f287e
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_arabic.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.375%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="7.500%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-11.500%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="9.375%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-9.375%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_arabic3"
+            latin:keyXPos="12.750%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..3637ce7
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_azerty.xml
@@ -0,0 +1,72 @@
+<?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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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:keyXPos="-13.750%p"
+            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..5f59f70
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_bulgarian.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="7.333%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_bulgarian1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.194%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="9.375%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="14.375%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..ba57b75
--- /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.125%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft" />
+        <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.125%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="9.375%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_east_slavic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-9.375%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.125%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="12.750%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_georgian.xml b/java/res/xml-sw768dp/rows_georgian.xml
new file mode 100644
index 0000000..0471e8f
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_georgian.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="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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..983abe1
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_greek.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/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:keyXPos="-15.704%p"
+            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:keyXPos="-13.750%p"
+            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..891872c
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_hebrew.xml
@@ -0,0 +1,65 @@
+<?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:keyXPos="-12.000%p"
+            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_hebrew2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.047%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_hebrew3"
+            latin:keyXPos="13.829%p" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-10.400%p"
+            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..3489724
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_nordic.xml
@@ -0,0 +1,72 @@
+<?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.500%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-11.500%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelFlags="alignLeft"
+            latin:keyWidth="9.375%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nordic2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyXPos="-9.375%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.375%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="12.750%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-12.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..8f0b762
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_qwerty.xml
@@ -0,0 +1,72 @@
+<?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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..e3d1c61
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_qwertz.xml
@@ -0,0 +1,72 @@
+<?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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..0de8ff8
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_south_slavic.xml
@@ -0,0 +1,67 @@
+<?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.125%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelFlags="alignLeft" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic1"
+            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-9.219%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.250%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:keyXPos="-15.704%p"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.250%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.829%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_south_slavic3" />
+        <include
+            latin:keyboardLayout="@xml/keys_comma_period" />
+    </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..e4690f3
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_spanish.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="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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..3902aef
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_symbols.xml
@@ -0,0 +1,71 @@
+<?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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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..7cfff37
--- /dev/null
+++ b/java/res/xml-sw768dp/rows_symbols_shift.xml
@@ -0,0 +1,71 @@
+<?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:keyXPos="-9.219%p"
+            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:keyXPos="-15.704%p"
+            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-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..da79758
--- /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"
+    latin:keyboardLocale="tr">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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..a7cfad1
--- /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"
+    latin:keyboardLocale="uk">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_east_slavic" />
+    <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..6d38eb1
--- /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"
+    latin:keyboardLocale="vi">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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-ar/kbd_symbols.xml b/java/res/xml/kbd_arabic.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_arabic.xml
index 9e5c255..ce5f30b 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_arabic" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml/kbd_azerty.xml
similarity index 89%
rename from java/res/xml-ar/kbd_symbols.xml
rename to java/res/xml/kbd_azerty.xml
index 9e5c255..7bafe5b 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_azerty" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_azerty_symbols.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/kbd_azerty_symbols.xml
index 7bdfbad..7e075df 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_azerty_symbols_shift.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/kbd_azerty_symbols_shift.xml
index 7bdfbad..25db3c8 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_symbols_shift" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_bulgarian.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/kbd_bulgarian.xml
index 7bdfbad..a651991 100644
--- a/java/res/xml-fr-rCA/kbd_qwerty.xml
+++ b/java/res/xml/kbd_bulgarian.xml
@@ -2,9 +2,9 @@
 <!--
 /*
 **
-** Copyright 2008, 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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_bulgarian" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml/kbd_east_slavic.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_east_slavic.xml
index 9e5c255..3bc2339 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_east_slavic" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_georgian.xml
similarity index 83%
rename from java/res/xml-fr-rCA/kbd_qwerty.xml
rename to java/res/xml/kbd_georgian.xml
index 7bdfbad..2dc6bf0 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_georgian" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_greek.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/kbd_greek.xml
index 7bdfbad..7056efb 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_greek" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml/kbd_hebrew.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_hebrew.xml
index 9e5c255..74836f3 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_hebrew" />
 </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-ar/kbd_symbols.xml b/java/res/xml/kbd_nordic.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_nordic.xml
index 9e5c255..a2196c9 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        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-ar/kbd_symbols.xml b/java/res/xml/kbd_qwertz.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_qwertz.xml
index 9e5c255..9f7e901 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        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-ar/kbd_symbols.xml b/java/res/xml/kbd_south_slavic.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_south_slavic.xml
index 9e5c255..f3ad02c 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_south_slavic" />
 </Keyboard>
diff --git a/java/res/xml-ar/kbd_symbols.xml b/java/res/xml/kbd_spanish.xml
similarity index 89%
copy from java/res/xml-ar/kbd_symbols.xml
copy to java/res/xml/kbd_spanish.xml
index 9e5c255..6ce2b5d 100644
--- a/java/res/xml-ar/kbd_symbols.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:isRtlKeyboard="true"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_symbols" />
+        latin:keyboardLayout="@xml/rows_spanish" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_spanish_symbols.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/kbd_spanish_symbols.xml
index 7bdfbad..7e075df 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/rows_symbols" />
 </Keyboard>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_spanish_symbols_shift.xml
similarity index 83%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/kbd_spanish_symbols_shift.xml
index 7bdfbad..25db3c8 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        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/kbd_settings_or_tab.xml b/java/res/xml/key_azerty_quote.xml
similarity index 63%
rename from java/res/xml/kbd_settings_or_tab.xml
rename to java/res/xml/key_azerty_quote.xml
index 4a8bcc7..e1d1804 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%
copy from java/res/xml/kbd_settings_or_tab.xml
copy 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..087b895
--- /dev/null
+++ b/java/res/xml/key_styles_common.xml
@@ -0,0 +1,202 @@
+<?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"
+                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" />
+    <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:keyLabel="&#x200C;"
+        latin:moreKeys="&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        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" />
+    <!-- U+200C: "" ZERO WIDTH NON-JOINER
+         U+200D: "" ZERO WIDTH JOINER -->
+    <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 91%
rename from java/res/xml/kbd_currency_key_styles.xml
rename to java/res/xml/key_styles_currency.xml
index 2258883..3e4afdf 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,19 +62,19 @@
             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"
@@ -121,7 +121,7 @@
         </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 100%
rename from java/res/xml/kbd_currency_dollar_key_styles.xml
rename to java/res/xml/key_styles_currency_dollar.xml
diff --git a/java/res/xml/kbd_currency_euro_key_styles.xml b/java/res/xml/key_styles_currency_euro.xml
similarity index 100%
rename from java/res/xml/kbd_currency_euro_key_styles.xml
rename to java/res/xml/key_styles_currency_euro.xml
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..1398b13
--- /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"
+    latin:keyboardLocale="en_GB,en_US">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_qwerty" />
+    <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/kbd_row3_comma_period.xml b/java/res/xml/keys_comma_period.xml
similarity index 81%
rename from java/res/xml-sw600dp/kbd_row3_comma_period.xml
rename to java/res/xml/keys_comma_period.xml
index b844430..6db02b6 100644
--- a/java/res/xml-sw600dp/kbd_row3_comma_period.xml
+++ b/java/res/xml/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.
@@ -33,14 +33,14 @@
         <default>
             <Key
                 latin:keyLabel=","
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="!"
-                latin:moreKeys="!" />
+                latin:moreKeys="!"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
             <Key
                 latin:keyLabel="."
-                latin:keyLabelOption="hasUppercaseLetter"
                 latin:keyHintLabel="\?"
-                latin:moreKeys="\?" />
+                latin:moreKeys="\?"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
 </merge>
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/method.xml b/java/res/xml/method.xml
index 6184add..cdcea0e 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -20,7 +20,47 @@
 <!-- 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_ZZ: 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
+    hr: Croatian/qwertz
+    hu: Hungarian/qwertz
+    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
+    tr: Turkish/qwerty
+    uk: Ukrainian/east_slavic
+    vi: Vietnamese/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,7 +71,7 @@
             android:label="@string/subtype_en_US"
             android:imeSubtypeLocale="en_US"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection,EnabledWhenDefaultIsNotAsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_en_GB"
@@ -47,6 +87,18 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="be"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="bg"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="cs"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
@@ -64,19 +116,37 @@
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
     />
     <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"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="el"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="es"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="et"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="fa"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fi"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
@@ -126,6 +196,36 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="ka"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="ky"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="lt"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="lv"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="mk"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="nb"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
@@ -150,12 +250,30 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="ro"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <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,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="sl"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="sr"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
@@ -172,4 +290,16 @@
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
     />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="uk"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="vi"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
 </input-method>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index dcaa202..ebca250 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -44,11 +44,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 +92,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 +120,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 +138,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..11bcd67
--- /dev/null
+++ b/java/res/xml/rowkeys_azerty2.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="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" />
+</merge>
diff --git a/java/res/xml/rowkeys_azerty3.xml b/java/res/xml/rowkeys_azerty3.xml
new file mode 100644
index 0000000..1a0eacc
--- /dev/null
+++ b/java/res/xml/rowkeys_azerty3.xml
@@ -0,0 +1,41 @@
+<?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" />
+    <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..ab83d99
--- /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_cyrillic_shcha"
+        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..20415bc
--- /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_cyrillic_yery"
+        latin:moreKeys="@string/more_keys_for_cyrillic_yery" />
+    <!-- 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..bb67223
--- /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_cyrillic_i" />
+    <!-- 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_georgian1.xml b/java/res/xml/rowkeys_georgian1.xml
new file mode 100644
index 0000000..b644cdf
--- /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|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..ddd375a
--- /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|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..ae8443a
--- /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|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-fr-rCA/kbd_qwerty.xml b/java/res/xml/rowkeys_nordic1.xml
similarity index 77%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/rowkeys_nordic1.xml
index 7bdfbad..355b626 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</Keyboard>
+        latin:keyboardLayout="@xml/rowkeys_qwerty1" />
+    <Key
+        latin:keyLabel="@string/keylabel_for_scandinavia_row1_11" />
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_qwerty.xml b/java/res/xml/rowkeys_nordic2.xml
similarity index 64%
rename from java/res/xml-sw768dp/kbd_rows_qwerty.xml
rename to java/res/xml/rowkeys_nordic2.xml
index 6237712..58e6bc2 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_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" />
 </merge>
diff --git a/java/res/xml/rowkeys_qwerty1.xml b/java/res/xml/rowkeys_qwerty1.xml
new file mode 100644
index 0000000..bd5e9a2
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty1.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"
+>
+    <Key
+        latin:keyLabel="q"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <Key
+        latin:keyLabel="w"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2" />
+    <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..4c983df
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty2.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="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" />
+</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..27b8a76
--- /dev/null
+++ b/java/res/xml/rowkeys_qwertz1.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"
+>
+    <Key
+        latin:keyLabel="q"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1" />
+    <Key
+        latin:keyLabel="w"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2" />
+    <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..5485d86
--- /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_cyrillic_ze"
+        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..f30f42c
--- /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_cyrillic_tshe" />
+</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..303fad8
--- /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_cyrillic_dze" />
+    <!-- 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_cyrillic_dje" />
+    <!-- U+0436: "ж" CYRILLIC SMALL LETTER ZHE -->
+    <Key
+        latin:keyLabel="&#x0436;" />
+</merge>
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/rowkeys_spanish2.xml
similarity index 75%
copy from java/res/xml-fr-rCA/kbd_qwerty.xml
copy to java/res/xml/rowkeys_spanish2.xml
index 7bdfbad..4c7e579 100644
--- a/java/res/xml-fr-rCA/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="fr_CA"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
-</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/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..0193612
--- /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.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="11.75%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/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/kbd_qwerty_row3.xml b/java/res/xml/rows_nordic.xml
similarity index 66%
copy from java/res/xml/kbd_qwerty_row3.xml
copy to java/res/xml/rows_nordic.xml
index c2b45e7..51d20e8 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/rows_nordic.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="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="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/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..31bb389
--- /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.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="11.75%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/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..41da2aa 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -17,18 +17,18 @@
 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;
 import android.provider.Settings;
+import android.support.v4.view.MotionEventCompat;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.compat.AccessibilityManagerCompatWrapper;
+import com.android.inputmethod.compat.AccessibilityManagerCompatUtils;
 import com.android.inputmethod.compat.AudioManagerCompatWrapper;
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.compat.MotionEventCompatUtils;
@@ -45,7 +45,6 @@
 
     private Context mContext;
     private AccessibilityManager mAccessibilityManager;
-    private AccessibilityManagerCompatWrapper mCompatManager;
     private AudioManagerCompatWrapper mAudioManager;
 
     /*
@@ -55,15 +54,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 +73,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 +93,7 @@
     public boolean isTouchExplorationEnabled() {
         return ENABLE_ACCESSIBILITY
                 && mAccessibilityManager.isEnabled()
-                && mCompatManager.isTouchExplorationEnabled();
+                && AccessibilityManagerCompatUtils.isTouchExplorationEnabled(mAccessibilityManager);
     }
 
     /**
@@ -111,17 +109,17 @@
 
         return action == MotionEventCompatUtils.ACTION_HOVER_ENTER
                 || action == MotionEventCompatUtils.ACTION_HOVER_EXIT
-                || action == MotionEventCompatUtils.ACTION_HOVER_MOVE;
+                || action == MotionEventCompat.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 +135,7 @@
             return false;
 
         // Don't speak if the IME is connected to a password field.
-        return InputTypeCompatUtils.isPasswordInputType(attribute.inputType);
+        return InputTypeCompatUtils.isPasswordInputType(editorInfo.inputType);
     }
 
     /**
@@ -171,11 +169,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..2401d93 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -17,35 +17,39 @@
 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.MotionEventCompat;
+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.compat.ViewParentCompatUtils;
 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 +57,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 +72,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 +127,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 MotionEventCompatUtils.ACTION_HOVER_EXIT:
+            return onHoverKey(key, event);
+        case MotionEventCompat.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(MotionEventCompatUtils.ACTION_HOVER_EXIT);
+        onHoverKey(previousKey, event);
+
+        event.setAction(MotionEventCompatUtils.ACTION_HOVER_ENTER);
+        onHoverKey(currentKey, event);
+
+        event.setAction(MotionEventCompat.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 MotionEventCompatUtils.ACTION_HOVER_ENTER:
+            sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+            break;
+        case MotionEventCompatUtils.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.
+        ViewParentCompatUtils.requestSendAccessibilityEvent(mView.getParent(), mView, event);
     }
 
     private class KeyboardFlickGestureDetector extends FlickGestureDetector {
@@ -185,4 +230,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..eaa4ddf 100644
--- a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
+++ b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.Message;
+import android.support.v4.view.MotionEventCompat;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
@@ -32,7 +33,7 @@
  * properties:
  * <ul>
  *   <li>Begins with a {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event
- *   <li>Contains any number of {@link MotionEventCompatUtils#ACTION_HOVER_MOVE}
+ *   <li>Contains any number of {@link MotionEventCompat#ACTION_HOVER_MOVE}
  *       events
  *   <li>Ends with a {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event
  *   <li>Maximum duration of 250 milliseconds
@@ -126,10 +127,9 @@
         }
 
         final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event);
-        final long timeout = event.getEventTime() - mCachedHoverEnter.getEventTime();
 
         switch (event.getAction()) {
-        case MotionEventCompatUtils.ACTION_HOVER_MOVE:
+        case MotionEventCompat.ACTION_HOVER_MOVE:
             // Consume all valid move events before timeout.
             return true;
         case MotionEventCompatUtils.ACTION_HOVER_EXIT:
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
index 6594935..2c31c55 100644
--- a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
@@ -24,7 +24,7 @@
 
     public AbstractCompatWrapper(Object obj) {
         if (obj == null) {
-            Log.e(TAG, "Invalid input to AbstructCompatWrapper");
+            Log.e(TAG, "Invalid input to AbstractCompatWrapper");
         }
         mObj = obj;
     }
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/AccessibilityManagerCompatUtils.java
similarity index 74%
rename from java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
rename to java/src/com/android/inputmethod/compat/AccessibilityManagerCompatUtils.java
index a30af0f..41b6a07 100644
--- a/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatUtils.java
@@ -20,17 +20,15 @@
 
 import java.lang.reflect.Method;
 
-public class AccessibilityManagerCompatWrapper {
+public class AccessibilityManagerCompatUtils {
     private static final Method METHOD_isTouchExplorationEnabled = CompatUtils.getMethod(
             AccessibilityManager.class, "isTouchExplorationEnabled");
 
-    private final AccessibilityManager mManager;
-
-    public AccessibilityManagerCompatWrapper(AccessibilityManager manager) {
-        mManager = manager;
+    private AccessibilityManagerCompatUtils() {
+        // This class is non-instantiable.
     }
 
-    public boolean isTouchExplorationEnabled() {
-        return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isTouchExplorationEnabled);
+    public static boolean isTouchExplorationEnabled(AccessibilityManager receiver) {
+        return (Boolean) CompatUtils.invoke(receiver, false, METHOD_isTouchExplorationEnabled);
     }
 }
diff --git a/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java b/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
index f6afbcf..011473b 100644
--- a/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
@@ -16,10 +16,14 @@
 
 package com.android.inputmethod.compat;
 
+import android.util.Log;
+
 import java.lang.reflect.Method;
 import java.util.Arrays;
 
 public class ArraysCompatUtils {
+    private static final String TAG = ArraysCompatUtils.class.getSimpleName();
+
     private static final Method METHOD_Arrays_binarySearch = CompatUtils
             .getMethod(Arrays.class, "binarySearch", int[].class, int.class, int.class, int.class);
 
@@ -33,8 +37,15 @@
         }
     }
 
-    /* package */ static int compatBinarySearch(int[] array, int startIndex, int endIndex,
-            int value) {
+    // TODO: Implement fast binary search
+    /* package for testing */
+    static int compatBinarySearch(int[] array, int startIndex, int endIndex, int value) {
+        // Output error log because this method has strict performance penalty.
+        // Note that this method has been called only from spell checker and spell checker exists
+        // only from IceCreamSandwich and after, so that there is no chance on pre-ICS device to
+        // invoke this method.
+        Log.e(TAG, "Invoked expensive binarySearch");
+
         if (startIndex > endIndex) throw new IllegalArgumentException();
         if (startIndex < 0 || endIndex > array.length) throw new ArrayIndexOutOfBoundsException();
 
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index bcdcef7..938388d 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -26,77 +26,88 @@
             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_FLAG_FORCE_ASCII = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_FORCE_ASCII");
     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_FLAG_FORCE_ASCII = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_FLAG_FORCE_ASCII);
     private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils
             .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS);
 
+    // EditorInfo.IME_FLAG_NAVIGATE_NEXT has been introduced since API#11 (Honeycomb).
     public static boolean hasFlagNavigateNext(int imeOptions) {
         if (OBJ_IME_FLAG_NAVIGATE_NEXT == null)
             return false;
         return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0;
     }
 
+    // EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS has been introduced since API#11 (Honeycomb).
     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);
+    // EditorInfo.IME_FLAG_FORCE_ASCII has been introduced since API#16 (JellyBean).
+    public static boolean hasFlagForceAscii(int imeOptions) {
+        if (OBJ_IME_FLAG_FORCE_ASCII == null)
+            return false;
+        return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0;
     }
 
+    // EditorInfo.IME_ACTION_PREVIOUS has been introduced since API#11 (Honeycomb).
     public static void performEditorActionPrevious(InputConnection ic) {
-        if (OBJ_IME_ACTION_PREVIOUS == null)
+        if (OBJ_IME_ACTION_PREVIOUS == null || ic == null)
             return;
         ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS);
     }
 
-    public static String imeOptionsName(int imeOptions) {
-        if (imeOptions == -1)
-            return null;
+    public static String imeActionName(int imeOptions) {
         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;
+        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";
+        default:
+            if (OBJ_IME_ACTION_PREVIOUS != null && actionId == OBJ_IME_ACTION_PREVIOUS) {
+                return "actionPrevious";
+            } else {
+                return "actionUnknown(" + actionId + ")";
             }
         }
+    }
+
+    public static String imeOptionsName(int imeOptions) {
+        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 (hasFlagNavigateNext(imeOptions)) {
+            flags.append("flagNavigateNext|");
+        }
+        if (hasFlagNavigatePrevious(imeOptions)) {
+            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/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 51dc4cd..bf5f201 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -32,7 +32,7 @@
 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 com.android.inputmethod.latin.SubtypeUtils;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -49,6 +49,8 @@
     private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
     private static final Method METHOD_getCurrentInputMethodSubtype =
             CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype");
+    private static final Method METHOD_getLastInputMethodSubtype =
+            CompatUtils.getMethod(InputMethodManager.class, "getLastInputMethodSubtype");
     private static final Method METHOD_getEnabledInputMethodSubtypeList =
             CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList",
                     InputMethodInfo.class, boolean.class);
@@ -60,6 +62,8 @@
                     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();
@@ -111,6 +115,15 @@
         return new InputMethodSubtypeCompatWrapper(o);
     }
 
+    public InputMethodSubtypeCompatWrapper getLastInputMethodSubtype() {
+        if (!SUBTYPE_SUPPORTED) {
+            return new InputMethodSubtypeCompatWrapper(
+                    0, 0, mLanguageSwitcherProxy.getInputLocale().toString(), KEYBOARD_MODE, "");
+        }
+        Object o = CompatUtils.invoke(mImm, null, METHOD_getLastInputMethodSubtype);
+        return new InputMethodSubtypeCompatWrapper(o);
+    }
+
     public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
             InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
         if (!SUBTYPE_SUPPORTED) {
@@ -150,11 +163,10 @@
     private InputMethodInfoCompatWrapper getLatinImeInputMethodInfo() {
         if (TextUtils.isEmpty(mLatinImePackageName))
             return null;
-        return Utils.getInputMethodInfo(this, mLatinImePackageName);
+        return SubtypeUtils.getInputMethodInfo(mLatinImePackageName);
     }
 
-    @SuppressWarnings("unused")
-    private InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
+    private static InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
         if (VOICE_MODE.equals(mode) && !FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES)
             return null;
         Locale inputLocale = SubtypeSwitcher.getInstance().getInputLocale();
@@ -222,6 +234,14 @@
         return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
     }
 
+    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+        if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) {
+            return true;
+        }
+        return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToNextInputMethod, token,
+                onlyCurrentIme);
+    }
+
     public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() {
         if (mImm == null) return null;
         List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>();
@@ -240,8 +260,8 @@
 
         // The code below are based on {@link InputMethodManager#showInputMethodMenuInternal}.
 
-        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
-                this, mLatinImePackageName);
+        final InputMethodInfoCompatWrapper myImi = SubtypeUtils.getInputMethodInfo(
+                mLatinImePackageName);
         final List<InputMethodSubtypeCompatWrapper> myImsList = getEnabledInputMethodSubtypeList(
                 myImi, true);
         final InputMethodSubtypeCompatWrapper currentIms = getCurrentInputMethodSubtype();
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
index a6bb83a..5741588 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
@@ -29,7 +29,7 @@
 
 // TODO: Override this class with the concrete implementation if we need to take care of the
 // performance.
-public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper {
+public 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";
@@ -65,7 +65,7 @@
 
     public InputMethodSubtypeCompatWrapper(Object subtype) {
         super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype))
-                ? subtype : null);
+                ? subtype : new Object());
         mDummyNameResId = 0;
         mDummyIconResId = 0;
         mDummyLocale = DEFAULT_LOCALE;
@@ -76,7 +76,7 @@
     // Constructor for creating a dummy subtype.
     public InputMethodSubtypeCompatWrapper(int nameResId, int iconResId, String locale,
             String mode, String extraValues) {
-        super(null);
+        super(new Object());
         if (DBG) {
             Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper");
         }
diff --git a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
index 8518a4a..9a52301 100644
--- a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.compat;
 
 public class MotionEventCompatUtils {
-    public static final int ACTION_HOVER_MOVE = 0x7;
+    // TODO: Remove after these are added to MotionEventCompat.
     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/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 161ef09..e82d914 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -50,19 +50,19 @@
             .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_SUGGESTIONS_MAX_SIZE
             = CompatUtils.getField(CLASS_SuggestionSpan, "SUGGESTIONS_MAX_SIZE");
     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);;
+    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)) {
                 throw new RuntimeException("Field is accidentially null.");
             }
         }
@@ -71,7 +71,7 @@
     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) {
             return text;
         }
         final Spannable spannable = text instanceof Spannable
@@ -93,8 +93,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;
         }
 
@@ -106,7 +105,7 @@
         }
         final ArrayList<String> suggestionsList = new ArrayList<String>();
         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);
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..6a0d4dd
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
@@ -0,0 +1,43 @@
+/*
+ * 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() {
+    }
+
+    /**
+     * Returns the flag value of the attributes of the suggestions that can be obtained by
+     * {@link #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/ViewParentCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewParentCompatUtils.java
new file mode 100644
index 0000000..d19bc3a
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/ViewParentCompatUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.compat;
+
+import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.lang.reflect.Method;
+
+public class ViewParentCompatUtils {
+    private static final Method METHOD_requestSendAccessibilityEvent = CompatUtils.getMethod(
+            ViewParent.class, "requestSendAccessibilityEvent", View.class,
+            AccessibilityEvent.class);
+
+    /**
+     * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+     * The child has already populated a record for itself in the event and is delegating
+     * to its parent to send the event. The parent can optionally add a record for itself.
+     * <p>
+     * Note: An accessibility event is fired by an individual view which populates the
+     *       event with a record for its state and requests from its parent to perform
+     *       the sending. The parent can optionally add a record for itself before
+     *       dispatching the request to its parent. A parent can also choose not to
+     *       respect the request for sending the event. The accessibility event is sent
+     *       by the topmost view in the view tree.</p>
+     *
+     * @param child The child which requests sending the event.
+     * @param event The event to be sent.
+     * @return True if the event was sent.
+     */
+    public static boolean requestSendAccessibilityEvent(
+            ViewParent receiver, View child, AccessibilityEvent event) {
+        return (Boolean) CompatUtils.invoke(
+                receiver, false, METHOD_requestSendAccessibilityEvent, child, event);
+    }
+}
diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index 3f8c2ef..87d1c11 100644
--- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -16,23 +16,6 @@
 
 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;
@@ -63,6 +46,25 @@
 import android.view.inputmethod.InputConnection;
 import android.widget.TextView;
 
+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.keyboard.LatinKeyboardView;
+import com.android.inputmethod.latin.EditingUtils;
+import com.android.inputmethod.latin.LastComposedWord;
+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.StringUtils;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Utils;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -90,6 +92,7 @@
     private static final boolean DEBUG = LatinImeLogger.sDBG;
 
     private boolean mAfterVoiceInput;
+    private boolean mConfigurationChanging;
     private boolean mHasUsedVoiceInput;
     private boolean mHasUsedVoiceInputUnsupportedLocale;
     private boolean mImmediatelyAfterVoiceInput;
@@ -157,11 +160,11 @@
         mPasswordText = isPasswordText;
     }
 
-    public void flushVoiceInputLogs(boolean configurationChanged) {
+    public void flushVoiceInputLogs() {
         if (!VOICE_INSTALLED) {
             return;
         }
-        if (!configurationChanged) {
+        if (!mConfigurationChanging) {
             if (mAfterVoiceInput) {
                 mVoiceInput.flushAllTextModificationCounters();
                 mVoiceInput.logInputEnded();
@@ -316,11 +319,11 @@
         mImmediatelyAfterVoiceInput = false;
     }
 
-    public void hideVoiceWindow(boolean configurationChanging) {
+    public void hideVoiceWindow() {
         if (!VOICE_INSTALLED) {
             return;
         }
-        if (!configurationChanging) {
+        if (!mConfigurationChanging) {
             if (mAfterVoiceInput)
                 mVoiceInput.logInputEnded();
             if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
@@ -433,44 +436,6 @@
         }
     }
 
-    /**
-     * 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;
@@ -552,7 +517,7 @@
         mHints.registerVoiceResult(bestResult);
 
         if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
-        mService.commitTyped(ic);
+        mService.commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
         EditingUtils.appendText(ic, bestResult);
         if (ic != null) ic.endBatchEdit();
 
@@ -695,12 +660,12 @@
                 && !mVoiceInput.isBlacklistedField(fieldContext);
     }
 
-    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo editorInfo) {
         @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);
+        final boolean noMic = StringUtils.inPrivateImeOptions(null,
+                LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)
+                || StringUtils.inPrivateImeOptions(mService.getPackageName(),
+                        LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo);
         return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
                 && SpeechRecognizer.isRecognitionAvailable(mService);
     }
@@ -709,7 +674,7 @@
         return SpeechRecognizer.isRecognitionAvailable(context);
     }
 
-    public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
+    public void loadSettings(EditorInfo editorInfo, SharedPreferences sp) {
         if (!VOICE_INSTALLED) {
             return;
         }
@@ -723,7 +688,7 @@
         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);
+                && shouldShowVoiceButton(makeFieldContext(), editorInfo);
         mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main));
     }
 
@@ -747,8 +712,9 @@
         // 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();
+            final LatinKeyboardView keyboardView = KeyboardSwitcher.getInstance().getKeyboardView();
+            if (keyboardView != null && keyboardView.isShown())
+                keyboardView.purgeKeyboardAndClosing();
             startListening(false, windowToken);
         }
         // If we have no token, onAttachedToWindow will take care of showing dialog and start
@@ -877,4 +843,12 @@
                 SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
                 DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
     }
+
+    public void startChangingConfiguration() {
+        mConfigurationChanging = true;
+    }
+
+    public void finishChangingConfiguration() {
+        mConfigurationChanging = false;
+    }
 }
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
index dbe7aec..421ee65 100644
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
@@ -16,16 +16,6 @@
 
 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;
@@ -37,6 +27,16 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
+import com.android.inputmethod.compat.SharedPreferencesCompat;
+import com.android.inputmethod.keyboard.KeyboardSet;
+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.StringUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.text.Collator;
 import java.util.ArrayList;
@@ -162,8 +162,8 @@
 
         try {
             final String localeStr = locale.toString();
-            final String[] layoutCountryCodes = KeyboardBuilder.parseKeyboardLocale(
-                    this, R.xml.kbd_qwerty).split(",", -1);
+            final String[] layoutCountryCodes = KeyboardSet.parseKeyboardLocale(
+                    getResources(), R.xml.keyboard_set).split(",", -1);
             if (!TextUtils.isEmpty(localeStr) && layoutCountryCodes.length > 0) {
                 for (String s : layoutCountryCodes) {
                     if (s.equals(localeStr)) {
@@ -237,12 +237,12 @@
 
             if (finalSize == 0) {
                 preprocess[finalSize++] =
-                        new LocaleEntry(Utils.getFullDisplayName(l, false), l);
+                        new LocaleEntry(StringUtils.getFullDisplayName(l, false), l);
             } else {
                 if (s.equals("zz_ZZ")) {
                     // ignore this locale
                 } else {
-                    final String displayName = Utils.getFullDisplayName(l, false);
+                    final String displayName = StringUtils.getFullDisplayName(l, false);
                     preprocess[finalSize++] = new LocaleEntry(displayName, l);
                 }
             }
diff --git a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
index 3c79cc2..fd2cf3d 100644
--- a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
@@ -43,10 +43,10 @@
 
     Bundle mFieldInfo;
 
-    public FieldContext(InputConnection conn, EditorInfo info,
+    public FieldContext(InputConnection conn, EditorInfo editorInfo,
             String selectedLanguage, String[] enabledLanguages) {
         mFieldInfo = new Bundle();
-        addEditorInfoToBundle(info, mFieldInfo);
+        addEditorInfoToBundle(editorInfo, mFieldInfo);
         addInputConnectionToBundle(conn, mFieldInfo);
         addLanguageInfoToBundle(selectedLanguage, enabledLanguages, mFieldInfo);
         if (DBG) Log.i("FieldContext", "Bundle = " + mFieldInfo.toString());
diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
index 71d15dc..ff8b1ab 100644
--- a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
@@ -16,10 +16,6 @@
 
 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;
@@ -39,6 +35,10 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
 import java.io.ByteArrayOutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -222,7 +222,7 @@
                 Locale locale = SubtypeSwitcher.getInstance().getInputLocale();
 
                 mLanguage.setVisibility(View.VISIBLE);
-                mLanguage.setText(Utils.getFullDisplayName(locale, true));
+                mLanguage.setText(StringUtils.getFullDisplayName(locale, true));
 
                 mPopupLayout.setBackgroundDrawable(mListeningBorder);
                 break;
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f1ae0b3..b09a275 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 = hashCode(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 = hashCode(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 hashCode(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..ea3f623 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -26,7 +26,7 @@
     private static final boolean DEBUG = false;
 
     public static final int NOT_A_CODE = -1;
-    public static final int NOT_A_KEY = -1;
+    private static final int ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE = 2;
 
     private final int mKeyHysteresisDistanceSquared;
 
@@ -39,7 +39,7 @@
     // 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];
+    private final Key[] mNeighborKeys = new Key[MAX_NEARBY_KEYS];
 
     /**
      * This class handles key detection.
@@ -65,11 +65,11 @@
         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;
     }
 
@@ -96,22 +96,22 @@
     }
 
     /**
-     * Computes maximum size of the array that can contain all nearby key indices returned by
-     * {@link #getKeyIndexAndNearbyCodes}.
+     * Computes maximum size of the array that can contain all nearby key codes returned by
+     * {@link #getNearbyCodes}.
      *
-     * @return Returns 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 codes returned
+     *         by {@link #getNearbyCodes}.
      */
     protected int getMaxNearbyKeys() {
         return MAX_NEARBY_KEYS;
     }
 
     /**
-     * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
+     * Allocates array that can hold all key codes returned by {@link #getNearbyCodes}
      * 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
+     * @return Allocates and returns an array that can hold all key codes returned by
+     *         {@link #getNearbyCodes} method. All elements in the returned array are
      *         initialized by {@link #NOT_A_CODE} value.
      */
     public int[] newCodeArray() {
@@ -122,7 +122,7 @@
 
     private void initializeNearbyKeys() {
         Arrays.fill(mDistances, Integer.MAX_VALUE);
-        Arrays.fill(mIndices, NOT_A_KEY);
+        Arrays.fill(mNeighborKeys, null);
     }
 
     /**
@@ -130,14 +130,14 @@
      * 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 key the key to be inserted into the nearby keys buffer.
      * @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) {
+    private int sortNearbyKeys(Key key, int distance, boolean isOnKey) {
         final int[] distances = mDistances;
-        final int[] indices = mIndices;
+        final Key[] neighborKeys = mNeighborKeys;
         for (int insertPos = 0; insertPos < distances.length; insertPos++) {
             final int comparingDistance = distances[insertPos];
             if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
@@ -145,79 +145,147 @@
                 if (nextPos < distances.length) {
                     System.arraycopy(distances, insertPos, distances, nextPos,
                             distances.length - nextPos);
-                    System.arraycopy(indices, insertPos, indices, nextPos,
-                            indices.length - nextPos);
+                    System.arraycopy(neighborKeys, insertPos, neighborKeys, nextPos,
+                            neighborKeys.length - nextPos);
                 }
                 distances[insertPos] = distance;
-                indices[insertPos] = keyIndex;
+                neighborKeys[insertPos] = key;
                 return insertPos;
             }
         }
         return distances.length;
     }
 
-    private void getNearbyKeyCodes(final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
-        final int[] indices = mIndices;
+    private void getNearbyKeyCodes(final int primaryCode, final int[] allCodes) {
+        final Key[] neighborKeys = mNeighborKeys;
+        final int maxCodesSize = allCodes.length;
 
         // allCodes[0] should always have the key code even if it is a non-letter key.
-        if (indices[0] == NOT_A_KEY) {
+        if (neighborKeys[0] == null) {
             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)
+        for (int j = 0; j < neighborKeys.length && numCodes < maxCodesSize; j++) {
+            final Key key = neighborKeys[j];
+            if (key == null)
                 break;
-            final int code = keys.get(index).mCode;
+            final int code = key.mCode;
             // filter out a non-letter key from nearby keys
             if (code < Keyboard.CODE_SPACE)
                 continue;
             allCodes[numCodes++] = code;
         }
+        if (maxCodesSize <= numCodes) {
+            return;
+        }
+
+        final int code = (primaryCode == NOT_A_CODE) ? allCodes[0] : primaryCode;
+        if (code == NOT_A_CODE) {
+            return;
+        }
+        final List<Integer> additionalChars = mKeyboard.getAdditionalProximityChars().get(code);
+        if (additionalChars == null || additionalChars.size() == 0) {
+            return;
+        }
+        int currentCodesSize = numCodes;
+        allCodes[numCodes++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+        if (maxCodesSize <= numCodes) {
+            return;
+        }
+        // TODO: This is O(N^2). Assuming additionalChars.size() is up to 4 or 5.
+        for (int i = 0; i < additionalChars.size(); ++i) {
+            final int additionalChar = additionalChars.get(i);
+            boolean contains = false;
+            for (int j = 0; j < currentCodesSize; ++j) {
+                if (additionalChar == allCodes[j]) {
+                    contains = true;
+                    break;
+                }
+            }
+            if (!contains) {
+                allCodes[numCodes++] = additionalChar;
+                if (maxCodesSize <= numCodes) {
+                    return;
+                }
+            }
+        }
     }
 
     /**
-     * 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
+     * Finds all possible nearby key codes around a touch event point and returns the nearest key.
+     * The algorithm to determine the nearby keys depends on the threshold set by
      * {@link #setProximityThreshold(int)} and the mode set by
      * {@link #setProximityCorrectionEnabled(boolean)}.
      *
      * @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
+     * @param allCodes All nearby key codes except functional key are returned in this array
      */
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    // TODO: Move this method to native code.
+    public void getNearbyCodes(int x, int y, final int[] allCodes) {
         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);
+        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;
+                final int insertedPosition = sortNearbyKeys(key, distance, isOnKey);
+                if (insertedPosition == 0 && isOnKey) {
+                    primaryKey = key;
+                }
             }
         }
 
-        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));
+        getNearbyKeyCodes(primaryKey != null ? primaryKey.mCode : NOT_A_CODE, allCodes);
+        if (DEBUG) {
+            Log.d(TAG, "x=" + x + " y=" + y
+                    + " primary=" + printableCode(primaryKey)
+                    + " codes=" + printableCodes(allCodes));
+        }
+    }
+
+    /**
+     * 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
+     * @return the key that the touch point hits.
+     */
+    public Key detectHitKey(int x, int y) {
+        final int touchX = getTouchX(x);
+        final int touchY = getTouchY(y);
+
+        for (final Key key : mKeyboard.getNearestKeys(touchX, touchY)) {
+            if (key.isOnKey(touchX, touchY)) {
+                return key;
             }
         }
+        return null;
+    }
 
-        return primaryIndex;
+    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 (code == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+                sb.append(" | ");
+                addDelimiter = false;
+            } else {
+                if (addDelimiter) sb.append(", ");
+                sb.append(Keyboard.printableCode(code));
+                addDelimiter = true;
+            }
+        }
+        return "[" + sb + "]";
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 4578507..cdf07ed 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,17 +16,34 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.graphics.drawable.Drawable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.text.TextUtils;
+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.XmlParseUtils;
 
-import java.util.Collections;
+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;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -47,7 +64,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 +83,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 +120,1205 @@
     /** 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;
 
-    public Keyboard(KeyboardParams params) {
+    private final Map<Integer, List<Integer>> mAdditionalProximityChars;
+
+    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;
+        mAdditionalProximityChars = params.mAdditionalProximityChars;
 
-        mProximityInfo = new ProximityInfo(
+        mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(),
                 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
-                mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);
+                mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection,
+                params.mAdditionalProximityChars);
     }
 
     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();
+        // TODO: Should be in Key instead of Keyboard.Params?
+        public final Map<Integer, List<Integer>> mAdditionalProximityChars =
+                new HashMap<Integer, List<Integer>>();
+
+        public KeyboardSet.KeysCache mKeysCache;
+
+        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;
+            }
         }
-        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 Map<Integer, List<Integer>> getAdditionalProximityChars() {
+        return mAdditionalProximityChars;
+    }
+
+    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_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="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 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;
+
+            setTouchPositionCorrectionData(context, params);
+            setAdditionalProximityChars(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, Params 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)
+                    Log.e(BUILDER_TAG, "touchPositionCorrectionData is not defined");
+                return;
+            }
+
+            final String[] data = context.getResources().getStringArray(resourceId);
+            params.mTouchPositionCorrection.load(data);
+        }
+
+        private static void setAdditionalProximityChars(Context context, Params params) {
+            final String[] additionalChars =
+                    context.getResources().getStringArray(R.array.additional_proximitychars);
+            int currentPrimaryIndex = 0;
+            for (int i = 0; i < additionalChars.length; ++i) {
+                final String additionalChar = additionalChars[i];
+                if (TextUtils.isEmpty(additionalChar)) {
+                    currentPrimaryIndex = 0;
+                } else if (currentPrimaryIndex == 0) {
+                    currentPrimaryIndex = additionalChar.charAt(0);
+                    params.mAdditionalProximityChars.put(
+                            currentPrimaryIndex, new ArrayList<Integer>());
+                } else if (currentPrimaryIndex != 0) {
+                    final int c = additionalChar.charAt(0);
+                    params.mAdditionalProximityChars.get(currentPrimaryIndex).add(c);
+                }
+            }
+        }
+
+        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;
+        }
+
+        public void setTouchPositionCorrectionEnabled(boolean enabled) {
+            mParams.mTouchPositionCorrection.setEnabled(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 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 Params 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.mMoreKeysTemplate = keyboardAttr.getResourceId(
+                        R.styleable.Keyboard_moreKeysTemplate, 0);
+                params.mMaxMoreKeysKeyboardColumn = 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) 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..16b4eaf 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 #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 #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..6703b93 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 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;
+    public KeyboardId(int elementId, Locale locale, int orientation, int width, int mode,
+            EditorInfo editorInfo, boolean clobberSettingsKey, boolean shortcutKeyEnabled,
+            boolean hasShortcutKey, boolean languageSwitchKeyEnabled) {
         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.mElementId = elementId;
+        this.mEditorInfo = editorInfo;
         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);
+        this.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
+        this.mCustomActionLabel = (editorInfo.actionLabel != null)
+                ? editorInfo.actionLabel.toString() : null;
 
-        this.mXmlName = xmlName;
-        this.mAttribute = attribute;
+        this.mHashCode = hashCode(this);
+    }
 
-        this.mHashCode = Arrays.hashCode(new Object[] {
-                locale,
-                orientation,
-                width,
-                mode,
-                xmlId,
-                mNavigateAction,
-                mPasswordInput,
-                hasSettingsKey,
-                f2KeyMode,
-                clobberSettingsKey,
-                shortcutKeyEnabled,
-                hasShortcutKey,
-                mImeAction,
+    private static int hashCode(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 == this.mOrientation
+                && other.mElementId == this.mElementId
+                && other.mMode == this.mMode
+                && other.mWidth == this.mWidth
+                && other.passwordInput() == this.passwordInput()
+                && other.mClobberSettingsKey == this.mClobberSettingsKey
+                && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled
+                && other.mHasShortcutKey == this.mHasShortcutKey
+                && other.mLanguageSwitchKeyEnabled == this.mLanguageSwitchKeyEnabled
+                && other.isMultiLine() == this.isMultiLine()
+                && other.imeAction() == this.imeAction()
+                && TextUtils.equals(other.mCustomActionLabel, this.mCustomActionLabel)
+                && other.navigateNext() == this.navigateNext()
+                && other.navigatePrevious() == this.navigatePrevious()
+                && other.mLocale.equals(this.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 EditorInfoCompatUtils.hasFlagNavigateNext(mEditorInfo.imeOptions);
     }
 
-    public boolean isPhoneKeyboard() {
-        return mMode == MODE_PHONE;
+    public boolean navigatePrevious() {
+        return EditorInfoCompatUtils.hasFlagNavigatePrevious(mEditorInfo.imeOptions);
     }
 
-    public boolean isPhoneShiftKeyboard() {
-        return mXmlId == R.xml.kbd_phone_shift;
+    public boolean passwordInput() {
+        final int inputType = mEditorInfo.inputType;
+        return InputTypeCompatUtils.isPasswordInputType(inputType)
+                || InputTypeCompatUtils.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..680ff0d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -0,0 +1,407 @@
+/*
+ * 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.compat.InputTypeCompatUtils;
+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.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;
+import java.util.Map;
+
+/**
+ * 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 = TAG;
+    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();
+
+    private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
+
+    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 Map<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 mVoiceKeyEnabled;
+        boolean mVoiceKeyOnMain;
+        boolean mNoSettingsKey;
+        boolean mLanguageSwitchKeyEnabled;
+        Locale mLocale;
+        int mOrientation;
+        int mWidth;
+        // KeyboardSet element id to keyboard layout XML id map.
+        final Map<Integer, Integer> mKeyboardSetElementIdToXmlIdMap =
+                new HashMap<Integer, Integer>();
+        Params() {}
+    }
+
+    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;
+        }
+
+        Integer keyboardXmlId = mParams.mKeyboardSetElementIdToXmlIdMap.get(keyboardSetElementId);
+        if (keyboardXmlId == null) {
+            keyboardXmlId = mParams.mKeyboardSetElementIdToXmlIdMap.get(
+                    KeyboardId.ELEMENT_ALPHABET);
+        }
+        final KeyboardId id = getKeyboardId(keyboardSetElementId);
+        try {
+            return getKeyboard(mContext, keyboardXmlId, id);
+        } catch (RuntimeException e) {
+            throw new KeyboardSetException(e, id);
+        }
+    }
+
+    private Keyboard getKeyboard(Context context, int keyboardXmlId, KeyboardId id) {
+        final Resources res = context.getResources();
+        final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
+        Keyboard keyboard = (ref == null) ? null : ref.get();
+        if (keyboard == null) {
+            final Locale savedLocale = LocaleUtils.setSystemLocale(res, id.mLocale);
+            try {
+                final Keyboard.Builder<Keyboard.Params> builder =
+                        new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params());
+                if (id.isAlphabetKeyboard()) {
+                    builder.setAutoGenerate(sKeysCache);
+                }
+                builder.load(keyboardXmlId, id);
+                builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled);
+                keyboard = builder.build();
+            } finally {
+                LocaleUtils.setSystemLocale(res, savedLocale);
+            }
+            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 hasShortcutKey = params.mVoiceKeyEnabled
+                && (isSymbols != params.mVoiceKeyOnMain);
+        return new KeyboardId(keyboardSetElementId, params.mLocale, params.mOrientation,
+                params.mWidth, params.mMode, params.mEditorInfo, params.mNoSettingsKey,
+                params.mVoiceKeyEnabled, 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();
+
+        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;
+        }
+
+        // TODO: Use InputMethodSubtype object as argument.
+        public Builder setSubtype(Locale inputLocale, boolean asciiCapable,
+                boolean touchPositionCorrectionEnabled) {
+            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;
+            mParams.mTouchPositionCorrectionEnabled = touchPositionCorrectionEnabled;
+            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 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 Locale savedLocale = LocaleUtils.setSystemLocale(mResources, mParams.mLocale);
+            try {
+                parseKeyboardSet(mResources, R.xml.keyboard_set);
+            } catch (Exception e) {
+                throw new RuntimeException(e.getMessage() + " in "
+                        + mResources.getResourceName(R.xml.keyboard_set)
+                        + " of locale " + mParams.mLocale);
+            } finally {
+                LocaleUtils.setSystemLocale(mResources, savedLocale);
+            }
+            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)) {
+                            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 int elementName = a.getInt(
+                        R.styleable.KeyboardSet_Element_elementName, 0);
+                final int elementKeyboard = a.getResourceId(
+                        R.styleable.KeyboardSet_Element_elementKeyboard, 0);
+                mParams.mKeyboardSetElementIdToXmlIdMap.put(elementName, elementKeyboard);
+            } 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 (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;
+            }
+        }
+    }
+
+    // TODO: Should be removed. This is no longer required if {@link InputMethodSubtype} is
+    // supported.
+    public static String parseKeyboardLocale(Resources res, int resId)
+            throws XmlPullParserException, IOException {
+        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_SET.equals(tag)) {
+                    final TypedArray keyboardSetAttr = res.obtainAttributes(
+                            Xml.asAttributeSet(parser), R.styleable.KeyboardSet);
+                    final String locale = keyboardSetAttr.getString(
+                            R.styleable.KeyboardSet_keyboardLocale);
+                    keyboardSetAttr.recycle();
+                    return locale;
+                } else {
+                    throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD_SET);
+                }
+            }
+        }
+        return "";
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ac718fc..42dd620 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,66 @@
 
     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),
+                mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
+                        LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
+        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 +175,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 +350,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..847174c 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;
@@ -42,24 +41,26 @@
 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 +70,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 +82,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,6 +95,8 @@
     // 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;
@@ -102,7 +105,6 @@
     private final int mKeyPreviewLayoutId;
     protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
     private boolean mShowKeyPreviewPopup = true;
-    private final int mDelayBeforePreview;
     private int mDelayAfterPreview;
     private ViewGroup mPreviewPlacer;
 
@@ -111,12 +113,12 @@
     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 */
@@ -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 {
+    /* package */ 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,17 @@
         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 +367,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 +441,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 +495,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 +592,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 +601,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 +620,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 +629,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,9 +656,9 @@
                 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.setTextAlign(Align.CENTER);
@@ -678,7 +671,7 @@
             }
             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,33 +696,38 @@
             }
             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;
+
+        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 reference, Paint paint) {
         final int labelSize = (int)paint.getTextSize();
@@ -746,42 +744,45 @@
         }
     }
 
-    private static float getCharHeight(char[] character, Paint paint) {
+    // Working variable for the following methods.
+    private final Rect mTextBounds = new Rect();
+
+    private float getCharHeight(char[] character, Paint paint) {
         final Integer key = getCharGeometryCacheKey(character[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(character, 0, 1, mTextBounds);
+        final float height = mTextBounds.height();
         sTextHeightCache.put(key, height);
         return height;
     }
 
-    private static float getCharWidth(char[] character, Paint paint) {
+    private float getCharWidth(char[] character, Paint paint) {
         final Integer key = getCharGeometryCacheKey(character[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(character, 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();
+    protected float getLabelWidth(CharSequence label, Paint paint) {
+        paint.getTextBounds(label.toString(), 0, label.length(), mTextBounds);
+        return mTextBounds.width();
     }
 
-    public float getDefaultLabelWidth(CharSequence label, Paint paint) {
+    public float getDefaultLabelWidth(String 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);
@@ -830,20 +831,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);
     }
 
@@ -858,7 +853,7 @@
                 keyPreview, FrameLayoutCompatUtils.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 +862,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 +872,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,12 +900,16 @@
         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
@@ -930,7 +928,8 @@
      * @see #invalidateKey(Key)
      */
     public void invalidateAllKeys() {
-        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mInvalidatedKeys.clear();
+        mInvalidateAllKeys = true;
         mBufferNeedsUpdate = true;
         invalidate();
     }
@@ -944,22 +943,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..97f4d07 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,21 +16,28 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 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;
@@ -38,11 +45,17 @@
 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.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 java.util.Locale;
 import java.util.WeakHashMap;
 
 /**
@@ -56,45 +69,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 ValueAnimator mLanguageOnSpacebarFadeoutAnimator;
+    private int mFinalAlphaOfLanguageOnSpacebar;
+    private static final int ALPHA_OPAQUE = 255;
+    private boolean mNeedsToDisplayLanguage;
+    private Locale mSpacebarLocale;
+    private int mSpacebarTextAlpha;
+    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 ValueAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
+    private ValueAnimator mAltCodeKeyWhileTypingFadeinAnimator;
+    private int mAltCodeKeyWhileTypingAnimAlpha;
+
+    // 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 +143,39 @@
             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:
+                final ValueAnimator fadeout = keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator;
+                if (fadeout != null && fadeout.isStarted()) {
+                    fadeout.cancel();
+                }
+                // TODO: Start the fade in animation with an initial value that is the same as the
+                // final value when the above fade out animation gets cancelled.
+                final ValueAnimator fadein = keyboardView.mAltCodeKeyWhileTypingFadeinAnimator;
+                if (fadein != null && !fadein.isStarted()) {
+                    fadein.start();
+                }
                 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 +188,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
@@ -139,19 +239,52 @@
         }
 
         @Override
-        public void cancelKeyTimers() {
-            cancelKeyRepeatTimer();
-            cancelLongPressTimer();
-            removeMessages(MSG_IGNORE_DOUBLE_TAP);
+        public void startTypingStateTimer() {
+            final boolean isTyping = isTypingState();
+            removeMessages(MSG_TYPING_STATE_EXPIRED);
+            sendMessageDelayed(
+                    obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
+            final LatinKeyboardView keyboardView = getOuterInstance();
+            if (isTyping) {
+                return;
+            }
+            final ValueAnimator fadein = keyboardView.mAltCodeKeyWhileTypingFadeinAnimator;
+            if (fadein != null && fadein.isStarted()) {
+                fadein.cancel();
+            }
+            // TODO: Start the fade out animation with an initial value that is the same as the
+            // final value when the above fade in animation gets cancelled.
+            final ValueAnimator fadeout = keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator;
+            if (fadeout != null && !fadeout.isStarted()) {
+                fadeout.start();
+            }
         }
 
-        public void startIgnoringDoubleTap() {
-            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
+        @Override
+        public boolean isTypingState() {
+            return hasMessages(MSG_TYPING_STATE_EXPIRED);
+        }
+
+        @Override
+        public void startDoubleTapTimer() {
+            sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
                     ViewConfiguration.getDoubleTapTimeout());
         }
 
-        public boolean isIgnoringDoubleTap() {
-            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
+        @Override
+        public void cancelDoubleTapTimer() {
+            removeMessages(MSG_DOUBLE_TAP);
+        }
+
+        @Override
+        public boolean isInDoubleTapTimeout() {
+            return hasMessages(MSG_DOUBLE_TAP);
+        }
+
+        @Override
+        public void cancelKeyTimers() {
+            cancelKeyRepeatTimer();
+            cancelLongPressTimer();
         }
 
         public void cancelAllMessages() {
@@ -159,52 +292,64 @@
         }
     }
 
-    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;
+
+        KeyTimerParams() {
+            mKeyRepeatStartTimeout = 0;
+            mKeyRepeatInterval = 0;
+            mLongPressKeyTimeout = 0;
+            mLongPressShiftKeyTimeout = 0;
+            mLongPressSpaceKeyTimeout = 0;
+            mIgnoreAltCodeKeyTimeout = 0;
+        }
+
+        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 +357,104 @@
 
         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);
+        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);
+
+        final ValueAnimator animator = loadValueAnimator(languageOnSpacebarFadeoutAnimatorResId);
+        if (animator != null) {
+            animator.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mSpacebarTextAlpha = (Integer)animation.getAnimatedValue();
+                    invalidateKey(mSpaceKey);
+                }
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator a) {
+                    final ValueAnimator valueAnimator = (ValueAnimator)a;
+                    mFinalAlphaOfLanguageOnSpacebar = (Integer)valueAnimator.getAnimatedValue();
+                }
+            });
+            // In order to get the final value of animator.
+            animator.end();
+        }
+        mLanguageOnSpacebarFadeoutAnimator = animator;
+
+        final ValueAnimator fadeout = loadValueAnimator(altCodeKeyWhileTypingFadeoutAnimatorResId);
+        if (fadeout != null) {
+            fadeout.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mAltCodeKeyWhileTypingAnimAlpha = (Integer)animation.getAnimatedValue();
+                    updateAltCodeKeyWhileTyping();
+                }
+            });
+            fadeout.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator a) {
+                    final ValueAnimator valueAnimator = (ValueAnimator)a;
+                }
+            });
+        }
+        mAltCodeKeyWhileTypingFadeoutAnimator = fadeout;
+
+        final ValueAnimator fadein = loadValueAnimator(altCodeKeyWhileTypingFadeinAnimatorResId);
+        if (fadein != null) {
+            fadein.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mAltCodeKeyWhileTypingAnimAlpha = (Integer)animation.getAnimatedValue();
+                    updateAltCodeKeyWhileTyping();
+                }
+            });
+            fadein.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator a) {
+                    final ValueAnimator valueAnimator = (ValueAnimator)a;
+                }
+            });
+        }
+        mAltCodeKeyWhileTypingFadeinAnimator = fadein;
     }
 
-    public void startIgnoringDoubleTap() {
-        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
-            mKeyTimerHandler.startIgnoringDoubleTap();
+    private ValueAnimator loadValueAnimator(int resId) {
+        if (resId == 0) return null;
+        return (ValueAnimator)AnimatorInflater.loadAnimator(getContext(), resId);
     }
 
     public void setKeyboardActionListener(KeyboardActionListener listener) {
@@ -264,20 +486,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.
@@ -296,6 +504,14 @@
         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;
+        mSpacebarTextAlpha = ALPHA_OPAQUE;
+        mAltCodeKeyWhileTypingAnimAlpha = ALPHA_OPAQUE;
     }
 
     /**
@@ -306,6 +522,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 +549,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 +558,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 +572,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 +593,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 +637,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 +690,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 +701,37 @@
             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)) {
+                UsabilityStudyLogUtils.getInstance().write(
+                        eventTag + eventTime + "," + id + "," + x + "," + y + ","
+                        + me.getSize(index) + "," + me.getPressure(index));
+            }
+        }
 
         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 +745,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 +761,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 +774,8 @@
 
         if (action == MotionEvent.ACTION_MOVE) {
             for (int i = 0; i < pointerCount; i++) {
-                final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
+                final PointerTracker tracker = PointerTracker.getPointerTracker(
+                        me.getPointerId(i), this);
                 final int px, py;
                 if (mMoreKeysPanel != null
                         && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
@@ -583,9 +786,15 @@
                     py = (int)me.getY(i);
                 }
                 tracker.onMoveEvent(px, py, eventTime);
+                if (ENABLE_USABILITY_STUDY_LOG) {
+                    UsabilityStudyLogUtils.getInstance().write("[Move]"  + eventTime + ","
+                            + me.getPointerId(i) + "," + px + "," + py + ","
+                            + me.getSize(i) + "," + me.getPressure(i));
+                }
             }
         } 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,7 +832,7 @@
                 super.draw(c);
                 tryGC = false;
             } catch (OutOfMemoryError e) {
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
+                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e);
             }
         }
     }
@@ -634,17 +843,6 @@
         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
@@ -654,13 +852,162 @@
      * @return {@code true} if the event was handled by the view, {@code false}
      *         otherwise
      */
+    //Should not annotate @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 ValueAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
+        if (animator != null) {
+           animator.cancel();
+        }
+        mNeedsToDisplayLanguage = needsToDisplayLanguage;
+        if (animator == null) {
+            mNeedsToDisplayLanguage = false;
+        } else {
+            if (subtypeChanged && needsToDisplayLanguage) {
+                mSpacebarTextAlpha = ALPHA_OPAQUE;
+                animator.start();
+            } else {
+                mSpacebarTextAlpha = mFinalAlphaOfLanguageOnSpacebar;
+            }
+        }
+        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(mSpacebarTextAlpha);
+            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
+            paint.setColor(mSpacebarTextColor);
+            paint.setAlpha(mSpacebarTextAlpha);
+            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..6c8d020 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;
@@ -41,24 +39,41 @@
     }
 
     @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    public void getNearbyCodes(int x, int y, final int[] allCodes) {
         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;
+        if (nearestKey != null) {
+            allCodes[0] = nearestKey.mCode;
+        } else {
+            allCodes[0] = NOT_A_CODE;
+        }
     }
-}
\ No newline at end of file
+
+    @Override
+    public Key detectHitKey(int x, int y) {
+        final int touchX = getTouchX(x);
+        final int touchY = getTouchY(y);
+
+        Key nearestKey = null;
+        int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
+        for (final Key key : getKeyboard().mKeys) {
+            final int dist = key.squaredDistanceToEdge(touchX, touchY);
+            if (dist < nearestDist) {
+                nearestKey = key;
+                nearestDist = dist;
+            }
+        }
+        return nearestKey;
+    }
+}
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..904a81d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -0,0 +1,364 @@
+/*
+ * 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));
+            Paint paint = null;
+            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) {
+                    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;
+        }
+
+        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 78%
rename from java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
rename to java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 471fb4e..2495b54 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,11 @@
 
     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) {
+            mListener.onCodeInput(primaryCode, x, y);
         }
 
         @Override
@@ -61,25 +61,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 +110,7 @@
 
     @Override
     public KeyboardActionListener getKeyboardActionListener() {
-        return mMiniKeyboardListener;
+        return mMoreKeysKeyboardListener;
     }
 
     @Override
@@ -124,26 +125,18 @@
 
     @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 pane = (MiniKeyboard)getKeyboard();
+        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()
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..61d75e2 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -17,20 +17,23 @@
 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.HashMap;
 import java.util.List;
+import java.util.Map;
 
 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 +44,19 @@
     // 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,
+            Map<Integer, List<Integer>> additionalProximityChars) {
+        if (TextUtils.isEmpty(localeStr)) {
+            mLocaleStr = "";
+        } else {
+            mLocaleStr = localeStr;
+        }
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -52,60 +64,69 @@
         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, additionalProximityChars);
     }
 
     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,
+                Collections.<Integer, List<Integer>> emptyMap());
     }
 
-    public static ProximityInfo createSpellCheckerProximityInfo() {
+    public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) {
         final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
         spellCheckerProximityInfo.mNativeProximityInfo =
-                spellCheckerProximityInfo.setProximityInfoNative(
-                        SpellCheckerProximityInfo.ROW_SIZE,
-                        480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY,
+                spellCheckerProximityInfo.setProximityInfoNative("",
+                        SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, (480 / 10), 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 +134,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 +183,17 @@
         }
     }
 
-    private void computeNearestNeighbors(int defaultWidth, List<Key> keys,
-            TouchPositionCorrection touchPositionCorrection) {
+    private void computeNearestNeighbors(int defaultWidth, final Key[] keys,
+            TouchPositionCorrection touchPositionCorrection,
+            Map<Integer, List<Integer>> additionalProximityChars) {
+        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 +201,51 @@
                 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;
+                int currentCodesSize = count;
+                for (int i = 0; i < currentCodesSize; ++i) {
+                    final int c = neighborKeys[i].mCode;
+                    final List<Integer> additionalChars = additionalProximityChars.get(c);
+                    if (additionalChars == null || additionalChars.size() == 0) {
+                        continue;
+                    }
+                    for (int j = 0; j < additionalChars.size(); ++j) {
+                        final int additionalChar = additionalChars.get(j);
+                        boolean contains = false;
+                        for (int k = 0; k < count; ++k) {
+                            if(additionalChar == neighborKeys[k].mCode) {
+                                contains = true;
+                                break;
+                            }
+                        }
+                        if (!contains) {
+                            neighborKeys[count++] = keyCodeMap.get(additionalChar);
+                        }
+                    }
+                }
+                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 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/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..9b9c861 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -23,86 +23,92 @@
 
 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 = 14;
 
-    // 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);
+    }
 
-    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..9c5ccc7
--- /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.compat.VibratorCompatWrapper;
+import com.android.inputmethod.keyboard.Keyboard;
+
+/**
+ * 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 VibratorCompatWrapper mVibrator;
+    private boolean mSoundOn;
+
+    public AudioAndHapticFeedbackManager(final LatinIME latinIme,
+            final SettingsValues settingsValues) {
+        mSettingsValues = settingsValues;
+        mVibrator = VibratorCompatWrapper.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 (mVibrator != null) {
+            mVibrator.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..9c35f8f 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -25,44 +25,25 @@
 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,
+    public static CharSequence computeAutoCorrectionWord(Map<String, Dictionary> dictionaries,
             WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
-            CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
+            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,
+                sortedScores, consideredWord, autoCorrectionThreshold)) {
+            return suggestions.get(0);
         }
+        return null;
     }
 
     public static boolean isValidWord(
@@ -74,6 +55,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;
@@ -98,34 +87,31 @@
         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(Map<String, Dictionary> dictionaries,
+            WordComposer wordComposer, ArrayList<CharSequence> 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) {
+    private static boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
+            ArrayList<CharSequence> suggestions, int[] sortedScores,
+            CharSequence consideredWord, double autoCorrectionThreshold) {
+        if (wordComposer.size() > 1 && suggestions.size() > 0 && sortedScores.length > 0) {
             final CharSequence autoCorrectionSuggestion = suggestions.get(0);
             final int autoCorrectionSuggestionScore = sortedScores[0];
             // TODO: when the normalized score of the first suggestion is nearly equals to
             //       the normalized score of the second suggestion, behave less aggressive.
-            mNormalizedScore = Utils.calcNormalizedScore(
-                    typedWord,autoCorrectionSuggestion, autoCorrectionSuggestionScore);
+            final double normalizedScore = BinaryDictionary.calcNormalizedScore(
+                    consideredWord.toString(), autoCorrectionSuggestion.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..31ff4e7 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -46,7 +46,7 @@
     private static final int TYPED_LETTER_MULTIPLIER = 2;
 
     private int mDicTypeId;
-    private int mNativeDict;
+    private long mNativeDict;
     private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
     private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
     private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
@@ -104,20 +104,24 @@
     }
 
     static {
-        Utils.loadNativeLibrary();
+        JniUtils.loadNativeLibrary();
     }
 
-    private native int openNative(String sourceDir, long dictOffset, long dictSize,
+    private native long 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 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);
+    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,
@@ -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) {
+            int[] alternatives = codes.getCodesAt(0);
+            System.arraycopy(alternatives, 0, mInputCodes, 0,
+                    Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+        }
 
         int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
                 mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
                 MAX_PROXIMITY_CHARS_SIZE);
 
         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);
             }
         }
     }
@@ -211,6 +212,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/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index b333e48..79441c5 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -75,7 +75,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 +93,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);
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..870b33f 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 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..79bf338 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -33,9 +33,8 @@
      */
     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.
@@ -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..c19a5a7 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -18,6 +18,8 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 
+import android.util.Log;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -27,7 +29,7 @@
  * Class for a collection of dictionaries that behave like one dictionary.
  */
 public class DictionaryCollection extends Dictionary {
-
+    private final String TAG = DictionaryCollection.class.getSimpleName();
     protected final List<Dictionary> mDictionaries;
 
     public DictionaryCollection() {
@@ -75,7 +77,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..7a81f7b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -164,7 +164,7 @@
         final Resources res = context.getResources();
         final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
 
-        final int resourceId = Utils.getMainDictionaryResourceId(res);
+        final int resourceId = getMainDictionaryResourceId(res);
         final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
         final boolean hasDictionary = isFullDictionary(afd);
         try {
@@ -182,7 +182,7 @@
         final Resources res = context.getResources();
         final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
 
-        final int resourceId = Utils.getMainDictionaryResourceId(res);
+        final int resourceId = getMainDictionaryResourceId(res);
         final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
         final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
                 ? afd.getLength()
@@ -209,4 +209,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..1e8ad18 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -87,23 +87,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 {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index cad69bb..8e8adc1 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -51,6 +51,7 @@
     private Object mUpdatingLock = new Object();
 
     private static class Node {
+        Node() {}
         char mCode;
         int mFrequency;
         boolean mTerminal;
@@ -300,7 +301,7 @@
                         finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
                     }
                     if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
-                            DataType.UNIGRAM)) {
+                            Dictionary.UNIGRAM)) {
                         return;
                     }
                 }
@@ -341,7 +342,7 @@
                                                 snr * addedAttenuation, mInputLength);
                                     }
                                     callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
-                                            DataType.UNIGRAM);
+                                            Dictionary.UNIGRAM);
                                 }
                             }
                             if (children != null) {
@@ -505,7 +506,7 @@
             } while (node != null);
 
             callback.addWord(mLookedUpString, index, MAX_WORD_LENGTH - index, freq, mDicTypeId,
-                    DataType.BIGRAM);
+                    Dictionary.BIGRAM);
         }
     }
 
@@ -547,6 +548,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..f7afed2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -0,0 +1,166 @@
+/*
+ * 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;
+
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+
+/**
+ * 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 (InputTypeCompatUtils.isPasswordInputType(inputType)
+                    || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)
+                    || InputTypeCompatUtils.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;
+        }
+    }
+
+    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/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..bc07924
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -0,0 +1,86 @@
+/*
+ * 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 java.util.ArrayList;
+
+/**
+ * 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 ArrayList<int[]> mCodes;
+    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 ArrayList<int[]> codes, final int[] xCoordinates,
+            final int[] yCoordinates, final String typedWord, final String committedWord,
+            final int separatorCode) {
+        mCodes = codes;
+        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 9c321bc..fa5a46f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -25,10 +25,12 @@
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 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;
@@ -38,10 +40,10 @@
 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.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
@@ -49,26 +51,29 @@
 import android.view.inputmethod.InputConnection;
 
 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.InputMethodSubtypeCompatWrapper;
 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.suggestions.SuggestionsView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -77,7 +82,6 @@
 public class LatinIME extends InputMethodServiceCompatWrapper 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;
 
@@ -107,7 +111,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";
 
     /**
@@ -143,6 +150,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;
@@ -157,51 +165,57 @@
         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 final KeyboardSwitcher mKeyboardSwitcher;
+    private final SubtypeSwitcher mSubtypeSwitcher;
     private VoiceProxy mVoiceProxy;
+    private boolean mShouldSwitchToLastSubtype = true;
 
     private UserDictionary mUserDictionary;
     private UserBigramDictionary mUserBigramDictionary;
     private UserUnigramDictionary mUserUnigramDictionary;
-    private boolean mIsUserDictionaryAvaliable;
+    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.
@@ -209,15 +223,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;
@@ -229,29 +235,21 @@
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
 
-    private final ComposingStateManager mComposingStateManager =
-            ComposingStateManager.getInstance();
+    private boolean mIsAutoCorrectionIndicatorOn;
 
     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);
@@ -259,27 +257,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();
@@ -291,23 +280,9 @@
                 latinIme.updateBigramPredictions();
                 break;
             case MSG_VOICE_RESULTS:
+                final Keyboard keyboard = switcher.getKeyboard();
                 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);
-                }
+                        || (keyboard != null && keyboard.isShiftedOrShiftLocked()));
                 break;
             }
         }
@@ -325,7 +300,7 @@
             return hasMessages(MSG_UPDATE_SUGGESTIONS);
         }
 
-        public void postUpdateShiftKeyState() {
+        public void postUpdateShiftState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
         }
@@ -347,30 +322,6 @@
             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);
@@ -384,21 +335,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);
@@ -416,18 +359,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;
@@ -435,30 +378,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) {
@@ -468,6 +413,7 @@
             } else {
                 final LatinIME latinIme = getOuterInstance();
                 latinIme.onFinishInputViewInternal(finishingInput);
+                mAppliedEditorInfo = null;
             }
         }
 
@@ -483,6 +429,12 @@
         }
     }
 
+    public LatinIME() {
+        super();
+        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+    }
+
     @Override
     public void onCreate() {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -492,15 +444,11 @@
         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;
 
@@ -509,6 +457,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) {
@@ -525,8 +477,8 @@
         // 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);
 
@@ -545,11 +497,9 @@
     // 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());
+        mSettingsValues = new SettingsValues(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
+        mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues);
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
-        updateSoundEffectVolume();
-        updateKeypressVibrationDuration();
     }
 
     private void initSuggest() {
@@ -566,7 +516,7 @@
             oldContactsDictionary = null;
         }
 
-        int mainDicResId = Utils.getMainDictionaryResourceId(res);
+        int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res);
         mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
         if (mSettingsValues.mAutoCorrectEnabled) {
             mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
@@ -574,7 +524,7 @@
 
         mUserDictionary = new UserDictionary(this, localeStr);
         mSuggest.setUserDictionary(mUserDictionary);
-        mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
+        mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
 
         resetContactsDictionary(oldContactsDictionary);
 
@@ -586,8 +536,6 @@
                 = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER_BIGRAM);
         mSuggest.setUserBigramDictionary(mUserBigramDictionary);
 
-        updateCorrectionMode();
-
         LocaleUtils.setSystemLocale(res, savedLocale);
     }
 
@@ -626,7 +574,7 @@
     /* package private */ void resetSuggestMainDict() {
         final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
         final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
-        int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+        int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(mResources);
         mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
     }
 
@@ -647,22 +595,21 @@
     @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;
+        mVoiceProxy.startChangingConfiguration();
         super.onConfigurationChanged(conf);
         mVoiceProxy.onConfigurationChanged(conf);
-        mConfigurationChanging = false;
+        mVoiceProxy.finishChangingConfiguration();
 
         // This will work only when the subtype is not supported.
         LanguageSwitcherProxy.onConfigurationChanged(conf);
@@ -695,13 +642,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
@@ -714,20 +661,39 @@
         mHandler.onFinishInput();
     }
 
-    private void onStartInputInternal(EditorInfo attribute, boolean restarting) {
-        super.onStartInput(attribute, restarting);
+    private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
+        super.onStartInput(editorInfo, restarting);
     }
 
-    private void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
-        super.onStartInputView(attribute, 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;
@@ -736,47 +702,45 @@
         // 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)
+        final int inputType = editorInfo.inputType;
+        mVoiceProxy.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);
+        mVoiceProxy.loadSettings(editorInfo, 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)
@@ -785,83 +749,17 @@
                 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());
+        mVoiceProxy.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();
@@ -874,7 +772,7 @@
 
         LatinImeLogger.commit();
 
-        mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
+        mVoiceProxy.flushVoiceInputLogs();
 
         KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.closing();
@@ -900,9 +798,9 @@
     @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 (DEBUG) {
             Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
@@ -911,51 +809,58 @@
                     + ", 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
@@ -997,7 +902,7 @@
             mOptionsDialog.dismiss();
             mOptionsDialog = null;
         }
-        mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
+        mVoiceProxy.hideVoiceWindow();
         super.hideWindow();
     }
 
@@ -1011,20 +916,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 List<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);
         }
     }
@@ -1032,8 +947,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);
@@ -1048,12 +965,34 @@
         setSuggestionStripShownInternal(shown, /* needsInputViewShown */true);
     }
 
+    private void adjustInputViewHeight() {
+        if (mKeyPreviewBackingView.getHeight() > 0) {
+            return;
+        }
+
+        final KeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+        if (keyboardView == null) return;
+        final int keyboardHeight = keyboardView.getHeight();
+        final int suggestionsHeight = mSuggestionsContainer.getHeight();
+        final int displayHeight = mResources.getDisplayMetrics().heightPixels;
+        final Rect rect = new Rect();
+        mKeyPreviewBackingView.getWindowVisibleDisplayFrame(rect);
+        final int notificationBarHeight = rect.top;
+        final int remainingHeight = displayHeight - notificationBarHeight - suggestionsHeight
+                - keyboardHeight;
+
+        final LayoutParams params = mKeyPreviewBackingView.getLayoutParams();
+        params.height = mSuggestionsView.setMoreSuggestionsHeight(remainingHeight);
+        mKeyPreviewBackingView.setLayoutParams(params);
+    }
+
     @Override
     public void onComputeInsets(InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
         final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView == null || mSuggestionsContainer == null)
             return;
+        adjustInputViewHeight();
         // In fullscreen mode, the height of the extract area managed by InputMethodService should
         // be considered.
         // See {@link android.inputmethodservice.InputMethodService#onComputeInsets}.
@@ -1065,7 +1004,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;
             }
@@ -1085,8 +1025,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
@@ -1124,9 +1066,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(),
@@ -1141,16 +1085,36 @@
         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();
+        }
+        mVoiceProxy.setVoiceInputHighlighted(false);
+    }
+
+    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,
+            addToUserUnigramAndBigramDictionaries(typedWord,
                     UserUnigramDictionary.FREQUENCY_FOR_TYPED);
         }
         updateSuggestions();
@@ -1166,60 +1130,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);
@@ -1235,19 +1180,16 @@
         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 */)) {
+        } else if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(
+                false /* exclude aux subtypes */)) {
             showOptionsMenu();
         } else {
             launchSettings();
@@ -1256,17 +1198,21 @@
 
     // 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;
     }
@@ -1275,91 +1221,121 @@
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
+    private static int getActionId(Keyboard keyboard) {
+        return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
+    }
+
+    private void performeEditorAction(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 InputMethodSubtypeCompatWrapper 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;
         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;
+            mShouldSwitchToLastSubtype = true;
             LatinImeLogger.logOnDelete();
             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:
+            performeEditorAction(getActionId(switcher.getKeyboard()));
+            break;
+        case Keyboard.CODE_ACTION_NEXT:
+            performeEditorAction(EditorInfo.IME_ACTION_NEXT);
+            break;
+        case Keyboard.CODE_ACTION_PREVIOUS:
+            EditorInfoCompatUtils.performEditorActionPrevious(getCurrentInputConnection());
+            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);
+                handleCharacter(primaryCode, x, y, 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
@@ -1368,15 +1344,38 @@
         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
@@ -1385,176 +1384,194 @@
         mKeyboardSwitcher.onCancelInput();
     }
 
-    private void handleBackspace(boolean justReplacedDoubleSpace) {
+    private void handleBackspace(final int spaceState) {
         if (mVoiceProxy.logAndRevertVoiceInput()) return;
-
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         ic.beginBatchEdit();
+        handleBackspaceWhileInBatchEdit(spaceState, ic);
+        ic.endBatchEdit();
+    }
 
+    // "ic" may not be null.
+    private void handleBackspaceWhileInBatchEdit(final int spaceState, final InputConnection ic) {
         mVoiceProxy.handleBackspace();
 
-        final boolean deleteChar = !mHasUncommittedTypedChars;
-        if (mHasUncommittedTypedChars) {
-            final int length = mComposingStringBuilder.length();
+        // In many cases, we may have to put the keyboard in auto-shift state again.
+        mHandler.postUpdateShiftState();
+
+        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) {
+        mVoiceProxy.handleCharacter();
+        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) {
+    // Returns true if we did an autocorrection, false otherwise.
+    private boolean handleSeparator(final int primaryCode, final int x, final int y,
+            final int spaceState) {
         mVoiceProxy.handleSeparator();
-        mComposingStateManager.onFinishComposingText();
 
         // Should dismiss the "Touch again to save" message when handling separator
         if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
@@ -1562,71 +1579,85 @@
             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());
+        commitTyped(getCurrentInputConnection(), LastComposedWord.NOT_A_SEPARATOR);
         mVoiceProxy.handleClose();
         requestHideSelf(0);
         LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
@@ -1635,7 +1666,7 @@
     }
 
     public boolean isSuggestionsRequested() {
-        return mIsSettingsSuggestionStripOn
+        return mInputAttributes.mIsSettingsSuggestionStripOn
                 && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
     }
 
@@ -1653,11 +1684,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();
     }
@@ -1681,36 +1712,27 @@
     }
 
     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);
         }
     }
 
@@ -1718,18 +1740,21 @@
         // Check if we have a suggestion engine attached.
         if ((mSuggest == null || !isSuggestionsRequested())
                 && !mVoiceProxy.isVoiceInputHighlighted()) {
+            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;
@@ -1738,27 +1763,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
@@ -1766,131 +1775,126 @@
         // 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);
+            commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
+                    separatorCodePoint);
             // Add the word to the user unigram dictionary if it's not a known word
-            addToUserUnigramAndBigramDictionaries(mBestWord,
+            addToUserUnigramAndBigramDictionaries(autoCorrection,
                     UserUnigramDictionary.FREQUENCY_FOR_TYPED);
-            return true;
+            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.
+                InputConnectionCompatUtils.commitCorrection(ic,
+                        mLastSelectionEnd - typedWord.length(), typedWord, autoCorrection);
+            }
         }
-        return false;
     }
 
     @Override
-    public void pickSuggestionManually(int index, CharSequence suggestion) {
-        mComposingStateManager.onFinishComposingText();
-        SuggestedWords suggestions = mSuggestionsView.getSuggestions();
+    public void pickSuggestionManually(final int index, final CharSequence suggestion) {
+        final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
         mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
                 mSettingsValues.mWordSeparators);
 
-        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);
+        commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
+                LastComposedWord.NOT_A_SEPARATOR);
         // Add the word to the auto dictionary if it's not a known word
         if (index == 0) {
             addToUserUnigramAndBigramDictionaries(suggestion,
@@ -1898,13 +1902,9 @@
         } 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();
-        }
+        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:
@@ -1922,13 +1922,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.
@@ -1936,26 +1931,21 @@
             // 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);
@@ -1967,11 +1957,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;
@@ -1981,22 +1974,31 @@
             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());
     }
 
@@ -2055,72 +2057,157 @@
     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;
     }
@@ -2129,12 +2216,6 @@
         return mSettingsValues.isWordSeparator(code);
     }
 
-    private void sendMagicSpace() {
-        sendKeyChar((char)Keyboard.CODE_SPACE);
-        mJustAddedMagicSpace = true;
-        mKeyboardSwitcher.updateShiftState();
-    }
-
     public boolean preferCapitalization() {
         return mWordComposer.isFirstCharCapitalized();
     }
@@ -2154,142 +2235,77 @@
             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);
-        }
-    }
-
+    // TODO: remove this method when VoiceProxy has been removed
     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);
-        }
+        mFeedbackManager.vibrate(mKeyboardSwitcher.getKeyboardView());
     }
 
-    public WordComposer getCurrentWord() {
-        return mWordComposer;
-    }
-
-    boolean isSoundOn() {
-        return mSettingsValues.mSoundOn && !mSilentModeOn;
+    public boolean isAutoCapitalized() {
+        return mWordComposer.isAutoCapitalized();
     }
 
     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;
@@ -2328,7 +2344,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);
@@ -2377,35 +2394,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..5390ee3 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,11 +16,11 @@
 
 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 com.android.inputmethod.keyboard.Keyboard;
 
 import java.util.List;
 
@@ -28,12 +28,13 @@
 
     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 +44,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) {
     }
@@ -67,10 +68,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) {
@@ -78,4 +82,8 @@
 
     public static void onPrintAllUsabilityStudyLogs() {
     }
+
+    public static boolean isResearcherPackage(Context context) {
+        return false;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 773efe7..72391f3 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -38,20 +38,16 @@
 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.inputmethodcommon.InputMethodSettingsActivity;
 
-import java.util.Arrays;
 import java.util.Locale;
 
 public class Settings extends InputMethodSettingsActivity
@@ -61,261 +57,47 @@
 
     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_SUBTYPES_SETTINGS = "subtype_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";
+
+    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";
+
     // 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;
-
-        // 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;
@@ -363,22 +145,21 @@
         final Context context = getActivityInternal();
 
         addPreferencesFromResource(R.xml.prefs);
-        mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
+        mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES_SETTINGS);
         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)
+        mVoiceOn = !(prefs.getString(PREF_VOICE_MODE, 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 +172,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);
@@ -412,13 +189,13 @@
         }
 
         if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
-            generalSettings.removePreference(findPreference(PREF_SUBTYPES));
+            generalSettings.removePreference(findPreference(PREF_SUBTYPES_SETTINGS));
         }
 
         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 +207,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 +219,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 +238,26 @@
             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 isResearcherPackage = LatinImeLogger.isResearcherPackage(this);
+        final boolean showUsabilityStudyModeOption =
+                res.getBoolean(R.bool.config_enable_usability_study_mode_option)
+                        || isResearcherPackage || ENABLE_EXPERIMENTAL_SETTINGS;
+        final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
+        if (!showUsabilityStudyModeOption) {
+            if (usabilityStudyPref != null) {
+                miscSettings.removePreference(usabilityStudyPref);
+            }
+        }
+        if (isResearcherPackage) {
+            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() {
@@ -521,20 +313,26 @@
     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)
+        if (key.equals(PREF_VOICE_MODE) && !mVoiceOn) {
+            if (!prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
                     .equals(mVoiceModeOff)) {
                 showVoiceConfirmation();
             }
-        } else if (key.equals(PREF_KEY_PREVIEW_POPUP_ON)) {
+        } else 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)
+        mVoiceOn = !(prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
                 .equals(mVoiceModeOff));
         updateVoiceModeSummary();
         updateShowCorrectionSuggestionsSummary();
@@ -545,10 +343,9 @@
     @Override
     public boolean onPreferenceClick(Preference pref) {
         if (pref == mInputLanguageSelection) {
-            startActivity(CompatUtils.getInputLanguageSelectionIntent(
-                    Utils.getInputMethodId(
-                            InputMethodManagerCompatWrapper.getInstance(),
-                            getActivityInternal().getApplicationInfo().packageName), 0));
+            final String imeId = SubtypeUtils.getInputMethodId(
+                    getActivityInternal().getApplicationInfo().packageName);
+            startActivity(CompatUtils.getInputLanguageSelectionIntent(imeId, 0));
             return true;
         }
         return false;
@@ -660,7 +457,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 +473,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 +485,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);
@@ -717,8 +514,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 +544,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 +571,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..4346a36
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -0,0 +1,362 @@
+/*
+ * 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.os.Build;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.keyboard.internal.KeySpecParser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+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 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);
+        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);
+
+        LocaleUtils.setSystemLocale(res, savedLocale);
+    }
+
+    // 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)));
+            }
+        }
+        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 = VibratorCompatWrapper.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
+                && !InputTypeCompatUtils.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;
+        }
+
+        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;
+    }
+
+    // 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;
+        }
+        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;
+    }
+
+    // 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/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 8a48620..ffbbf9b 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -35,7 +35,6 @@
 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;
@@ -47,8 +46,8 @@
     private static boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
 
+    public static final String KEYBOARD_MODE = "keyboard";
     private static final char LOCALE_SEPARATER = '_';
-    private static final String KEYBOARD_MODE = "keyboard";
     private static final String VOICE_MODE = "voice";
     private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
             "requireNetworkConnectivity";
@@ -421,11 +420,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();
     }
 
     //////////////////////////////////
@@ -517,7 +512,7 @@
     }
 
     public String getInputLanguageName() {
-        return Utils.getDisplayLanguage(getInputLocale());
+        return StringUtils.getDisplayLanguage(getInputLocale());
     }
 
     /////////////////////////////
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..cb2bcf4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
@@ -0,0 +1,135 @@
+/*
+ * 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 com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+
+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,
+            InputMethodSubtypeCompatWrapper ims) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        final InputMethodInfoCompatWrapper myImi = getInputMethodInfo(context.getPackageName());
+        final List<InputMethodSubtypeCompatWrapper> subtypes =
+                imm.getEnabledInputMethodSubtypeList(myImi, true);
+        for (final InputMethodSubtypeCompatWrapper 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<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
+    }
+
+    public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodInfoCompatWrapper myImi = getInputMethodInfo(context.getPackageName());
+        final List<InputMethodInfoCompatWrapper> imiList = Collections.singletonList(myImi);
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
+    }
+
+    private static boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
+            List<InputMethodInfoCompatWrapper> imiList) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        // Number of the filtered IMEs
+        int filteredImisCount = 0;
+
+        for (InputMethodInfoCompatWrapper imi : imiList) {
+            // 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;
+            }
+        }
+
+        if (filteredImisCount > 1) {
+            return true;
+        }
+        final List<InputMethodSubtypeCompatWrapper> 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 (InputMethodSubtypeCompatWrapper 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 InputMethodInfoCompatWrapper getInputMethodInfo(String packageName) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) {
+            throw new RuntimeException("Input method manager not found");
+        }
+
+        for (final InputMethodInfoCompatWrapper 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..69754d7 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -20,7 +20,9 @@
 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;
@@ -42,9 +44,8 @@
     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
@@ -83,8 +84,6 @@
 
     private static final boolean DBG = LatinImeLogger.sDBG;
 
-    private AutoCorrection mAutoCorrection;
-
     private Dictionary mMainDict;
     private ContactsDictionary mContactsDict;
     private WhitelistDictionary mWhiteListDictionary;
@@ -100,14 +99,15 @@
     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<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
+    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 +116,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 +142,7 @@
         initWhitelistAndAutocorrectAndPool(context, locale);
     }
 
-    private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
+    private static void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
             Dictionary dict) {
         final Dictionary oldDict = (dict == null)
                 ? dictionaries.remove(key)
@@ -169,14 +167,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() {
@@ -191,7 +181,7 @@
         return mUnigramDictionaries;
     }
 
-    public int getApproxMaxWordLength() {
+    public static int getApproxMaxWordLength() {
         return APPROX_MAX_WORD_LENGTH;
     }
 
@@ -226,44 +216,11 @@
         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());
@@ -277,41 +234,73 @@
     }
 
     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());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         sb.append(bigram);
         mSuggestions.add(sb);
     }
 
-    // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
-    public SuggestedWords.Builder getSuggestedWordBuilder(
-            final WordComposer wordComposer, CharSequence prevWordForBigram,
-            final ProximityInfo proximityInfo) {
+    private static final WordComposer sEmptyWordComposer = new WordComposer();
+    public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mAutoCorrection.init();
-        mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
-        mIsAllUpperCase = wordComposer.isAllUpperCase();
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
+        mIsFirstCharCapitalized = false;
+        mIsAllUpperCase = false;
+        mTrailingSingleQuotesCount = 0;
+        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
-        // 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;
+        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
+        LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
+        mConsideredWord = "";
 
-        if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
-                || mCorrectionMode == CORRECTION_BASIC)) {
+        Arrays.fill(mBigramScores, 0);
+        mBigramSuggestions = new ArrayList<CharSequence>(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));
+        }
+
+        StringUtils.removeDupes(mSuggestions);
+
+        return new SuggestedWords(SuggestedWords.getFromCharSequenceList(mSuggestions),
+                false /* typedWordValid */,
+                false /* hasAutoCorrectionCandidate */,
+                false /* allowsToBeAutoCorrected */,
+                false /* isPunctuationSuggestions */,
+                false /* isObsoleteSuggestions */);
+    }
+
+    // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
+    public SuggestedWords getSuggestedWords(
+            final WordComposer wordComposer, CharSequence prevWordForBigram,
+            final ProximityInfo proximityInfo, final int correctionMode) {
+        LatinImeLogger.onStartSuggestion(prevWordForBigram);
+        mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
+        mIsAllUpperCase = wordComposer.isAllUpperCase();
+        mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
+        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
+        Arrays.fill(mScores, 0);
+
+        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 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
             // At first character typed, search only the bigrams
             Arrays.fill(mBigramScores, 0);
-            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+            mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
 
             if (!TextUtils.isEmpty(prevWordForBigram)) {
                 CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@@ -321,7 +310,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,8 +318,7 @@
                     }
                 } 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.
                     final char currentCharUpper = Character.toUpperCase(currentChar);
                     int count = 0;
@@ -348,70 +336,126 @@
             }
 
         } 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))
                     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, mScores, 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, sb.toString());
+            } else {
+                mSuggestions.add(0, whitelistedWord);
             }
-            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, typedWord);
+        StringUtils.removeDupes(mSuggestions);
+
+        final ArrayList<SuggestedWordInfo> suggestionsList;
+        if (DBG) {
+            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions, mScores);
+        } else {
+            suggestionsList = SuggestedWords.getFromCharSequenceList(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();
+    // This assumes the scores[] array is at least as long as suggestions.size() - 1.
+    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
+            final String typedWord, final ArrayList<CharSequence> suggestions, final int[] scores) {
+        // TODO: this doesn't take into account the fact that removing dupes from mSuggestions
+        // may have made mScores[] and mSuggestions out of sync.
+        final CharSequence autoCorrectionSuggestion = suggestions.get(0);
+        double normalizedScore = BinaryDictionary.calcNormalizedScore(
+                typedWord, autoCorrectionSuggestion.toString(), scores[0]);
+        final int suggestionsSize = suggestions.size();
+        final ArrayList<SuggestedWordInfo> suggestionsList =
+                new ArrayList<SuggestedWordInfo>(suggestionsSize);
+        suggestionsList.add(new SuggestedWordInfo(autoCorrectionSuggestion, "+"));
+        // 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 < scores.length && i < suggestionsSize - 1; ++i) {
+            final String scoreInfoString;
+            if (normalizedScore > 0) {
+                scoreInfoString = String.format("%d (%4.2f)", scores[i], normalizedScore);
+                normalizedScore = 0.0;
+            } else {
+                scoreInfoString = Integer.toString(scores[i]);
+            }
+            suggestionsList.add(new SuggestedWordInfo(suggestions.get(i + 1), scoreInfoString));
+        }
+        for (int i = scores.length; i < suggestionsSize; ++i) {
+            suggestionsList.add(new SuggestedWordInfo(suggestions.get(i), "--"));
+        }
+        return suggestionsList;
     }
 
     @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 int dicTypeId, final int dataType) {
+        int dataTypeForLog = dataType;
         final ArrayList<CharSequence> suggestions;
         final int[] sortedScores;
         final int prefMaxSuggestions;
-        if(dataType == Dictionary.DataType.BIGRAM) {
+        if (dataType == Dictionary.BIGRAM) {
             suggestions = mBigramSuggestions;
             sortedScores = mBigramScores;
             prefMaxSuggestions = PREF_MAX_BIGRAMS;
@@ -424,7 +468,7 @@
         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) {
@@ -433,17 +477,17 @@
                 // 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)
+                if (StringUtils.equalsIgnoreCase(currentHighestWord, word, offset, length)
                         && score <= sortedScores[0]) {
                     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])
                             / MAXIMUM_BIGRAM_FREQUENCY)
@@ -474,7 +518,7 @@
 
         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,12 +530,12 @@
         } else {
             sb.append(word, offset, length);
         }
+        for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+            sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+        }
         suggestions.add(pos, sb);
         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);
         }
@@ -502,39 +546,22 @@
         // 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).length() == 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).charAt(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>();
         dictionaries.addAll(mUnigramDictionaries.values());
@@ -544,4 +571,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..b63bc6c 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -20,211 +20,131 @@
 import android.view.inputmethod.CompletionInfo;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 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(
+            Collections.<SuggestedWordInfo>emptyList(), false, false, false, false, false);
 
-    public final List<CharSequence> mWords;
     public final boolean mTypedWordValid;
     public final boolean mHasAutoCorrectionCandidate;
     public final boolean mIsPunctuationSuggestions;
+    public final boolean mAllowsToBeAutoCorrected;
+    public final boolean mIsObsoleteSuggestions;
     private final List<SuggestedWordInfo> mSuggestedWordInfoList;
-    private boolean mShouldBlockAutoCorrection;
 
-    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 List<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 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> getFromCharSequenceList(
+            final List<CharSequence> wordList) {
+        final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+        for (CharSequence word : wordList) {
+            if (null != word) result.add(new SuggestedWordInfo(word));
+        }
+        return result;
     }
 
-    public boolean shouldBlockAutoCorrection() {
-        return mShouldBlockAutoCorrection;
+    public static List<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()));
+        }
+        return result;
     }
 
-    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 Builder() {
-            // Nothing to do here.
-        }
-
-        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);
+    // 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));
+        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)) {
+                suggestionsList.add(new SuggestedWordInfo(prevWord));
+                alreadySeen.add(prevWord);
             }
-            return this;
+        }
+        return suggestionsList;
+    }
+
+    public static class SuggestedWordInfo {
+        public final CharSequence mWord;
+        private final String mDebugString;
+
+        public SuggestedWordInfo(final CharSequence word) {
+            mWord = word;
+            mDebugString = "";
         }
 
-        public Builder addWord(CharSequence word) {
-            return addWord(word, null, false);
+        public SuggestedWordInfo(final CharSequence word, final String debugString) {
+            mWord = word;
+            if (null == debugString) throw new NullPointerException("");
+            mDebugString = debugString;
         }
 
-        public Builder addWord(CharSequence word, CharSequence debugString,
-                boolean isPreviousSuggestedWord) {
-            SuggestedWordInfo info = new SuggestedWordInfo(debugString, isPreviousSuggestedWord);
-            return addWord(word, info);
-        }
-
-        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 String getDebugString() {
+            return mDebugString;
         }
 
         @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 mWord.toString();
             } else {
-                return mDebugString.toString();
+                return mWord.toString() + " (" + mDebugString.toString() + ")";
             }
         }
-
-        public boolean isObsoleteSuggestedWord () {
-            return mPreviousSuggestedWord;
-        }
     }
 }
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/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 9e65667..e6a59d0 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -159,7 +159,7 @@
      */
     public int addBigrams(String word1, String word2) {
         // remove caps if second word is autocapitalized
-        if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
+        if (mIme != null && mIme.isAutoCapitalized()) {
             word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
         }
         // Do not insert a word as a bigram of itself
@@ -212,7 +212,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);
@@ -238,7 +239,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 +250,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,7 +317,7 @@
         }
 
         /** 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);
@@ -344,7 +351,18 @@
 
         @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();
@@ -380,7 +398,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 +406,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/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/UserUnigramDictionary.java b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
index e41230b..869865d 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -121,7 +121,8 @@
     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 });
+        final Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
+        if (null == cursor) return;
         try {
             if (cursor.moveToFirst()) {
                 int wordIndex = cursor.getColumnIndex(COLUMN_WORD);
@@ -149,7 +150,7 @@
         final int length = word.length();
         // Don't add very short or very long words.
         if (length < 2 || length > getMaxWordLength()) return;
-        if (mIme.getCurrentWord().isAutoCapitalized()) {
+        if (mIme.isAutoCapitalized()) {
             // Remove caps before adding
             word = Character.toLowerCase(word.charAt(0)) + word.substring(1);
         }
@@ -172,7 +173,7 @@
             // Nothing pending? Return
             if (mPendingWrites.isEmpty()) return;
             // Create a background thread to write the pending entries
-            new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute();
+            new UpdateDbTask(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>();
         }
@@ -206,16 +207,22 @@
         }
     }
 
-    private Cursor query(String selection, String[] selectionArgs) {
+    private static 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;
+        try {
+            SQLiteDatabase db = sOpenHelper.getReadableDatabase();
+            Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
+                    DEFAULT_SORT_ORDER);
+            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;
+        }
     }
 
     /**
@@ -227,8 +234,8 @@
         private final DatabaseHelper mDbHelper;
         private final String mLocale;
 
-        public UpdateDbTask(@SuppressWarnings("unused") Context context, DatabaseHelper openHelper,
-                HashMap<String, Integer> pendingWrites, String locale) {
+        public UpdateDbTask(DatabaseHelper openHelper, HashMap<String, Integer> pendingWrites,
+                String locale) {
             mMap = pendingWrites;
             mLocale = locale;
             mDbHelper = openHelper;
@@ -236,7 +243,14 @@
 
         @Override
         protected Void doInBackground(Void... v) {
-            SQLiteDatabase db = mDbHelper.getWritableDatabase();
+            SQLiteDatabase db = null;
+            try {
+                db = mDbHelper.getWritableDatabase();
+            } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+                // With no access to the DB, this is moot. Do nothing: we'll exit through the
+                // test for null == db.
+            }
+            if (null == db) return null;
             // Write all the entries to the db
             Set<Entry<String,Integer>> mEntries = mMap.entrySet();
             for (Entry<String,Integer> entry : mEntries) {
@@ -251,7 +265,7 @@
             return null;
         }
 
-        private ContentValues getContentValues(String word, int frequency, String locale) {
+        private static ContentValues getContentValues(String word, int frequency, String locale) {
             ContentValues values = new ContentValues(4);
             values.put(COLUMN_WORD, word);
             values.put(COLUMN_FREQUENCY, frequency);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index b29ff19..a7de47c 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -17,48 +17,36 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 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;
 
 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 +100,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';
@@ -242,10 +128,11 @@
             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) {
@@ -317,49 +204,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,55 +217,6 @@
         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 {
         private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
         private static final String FILENAME = "log.txt";
@@ -437,7 +232,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,7 +260,7 @@
             }
         }
 
-        public void writeBackSpace() {
+        public static void writeBackSpace() {
             UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
         }
 
@@ -504,32 +299,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 +422,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 +432,37 @@
         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--;
-                    break;
-                }
-            }
-            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();
         }
     }
 
-    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();
+    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;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index adc5637..126ac47 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,15 @@
 
 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 com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService;
+import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
 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 +34,31 @@
     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;
+    final static int N = BinaryDictionary.MAX_WORD_LENGTH;
 
+    private ArrayList<int[]> mCodes;
     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;
+
     /**
      * 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);
         mTypedWord = new StringBuilder(N);
         mXCoordinates = new int[N];
         mYCoordinates = new int[N];
+        mAutoCorrection = null;
+        mTrailingSingleQuotesCount = 0;
     }
 
     public WordComposer(WordComposer source) {
@@ -62,11 +68,12 @@
     public void init(WordComposer source) {
         mCodes = new ArrayList<int[]>(source.mCodes);
         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;
     }
 
     /**
@@ -75,8 +82,10 @@
     public void reset() {
         mCodes.clear();
         mTypedWord.setLength(0);
+        mAutoCorrection = null;
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
+        mTrailingSingleQuotesCount = 0;
     }
 
     /**
@@ -84,7 +93,11 @@
      * @return the number of keystrokes
      */
     public final int size() {
-        return mTypedWord.length();
+        return mCodes.size();
+    }
+
+    public final boolean isComposingWord() {
+        return mCodes.size() > 0;
     }
 
     /**
@@ -109,23 +122,120 @@
         return previous && !Character.isUpperCase(codePoint);
     }
 
+    // TODO: remove input keyDetector
+    public void add(int primaryCode, int x, int y, KeyDetector keyDetector) {
+        final int[] codes;
+        final int keyX;
+        final int keyY;
+        if (x == KeyboardActionListener.SPELL_CHECKER_COORDINATE
+                || y == KeyboardActionListener.SPELL_CHECKER_COORDINATE) {
+            // only used for tests in InputLogicTests
+            addKeyForSpellChecker(primaryCode, AndroidSpellCheckerService.SCRIPT_LATIN);
+            return;
+        } else if (x == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+                || y == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+                || x == KeyboardActionListener.NOT_A_TOUCH_COORDINATE
+                || y == KeyboardActionListener.NOT_A_TOUCH_COORDINATE) {
+            codes = new int[] { primaryCode };
+            keyX = x;
+            keyY = y;
+        } else {
+            codes = keyDetector.newCodeArray();
+            keyDetector.getNearbyCodes(x, y, codes);
+            keyX = keyDetector.getTouchX(x);
+            keyY = keyDetector.getTouchY(y);
+        }
+        add(primaryCode, codes, keyX, keyY);
+    }
+
+    // TODO: remove this function
+    public void addKeyForSpellChecker(int primaryCode, int script) {
+        final int[] proximities;
+        final int proximityIndex =
+                SpellCheckerProximityInfo.getIndexOfCodeForScript(primaryCode, script);
+        if (-1 == proximityIndex) {
+            proximities = new int[] { primaryCode };
+        } else {
+            // TODO: an initial examination seems to reveal this is actually used
+            // read-only. It should be possible to compute the arrays statically once
+            // and skip doing a copy each time here.
+            proximities = Arrays.copyOfRange(
+                    SpellCheckerProximityInfo.getProximityForScript(script),
+                    proximityIndex,
+                    proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
+        }
+        add(primaryCode, proximities,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+    }
+
     /**
      * 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
      */
-    public void add(int primaryCode, int[] codes, int x, int y) {
-        final int newIndex = size();
-        mTypedWord.append((char) primaryCode);
+    private void add(int primaryCode, int[] codes, int keyX, int keyY) {
+        final int newIndex = mCodes.size();
+        mTypedWord.appendCodePoint(primaryCode);
         correctPrimaryJuxtapos(primaryCode, codes);
         mCodes.add(codes);
         if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
-            mXCoordinates[newIndex] = x;
-            mYCoordinates[newIndex] = y;
+            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;
+    }
+
+    /**
+     * Internal method to retrieve reasonable proximity info for a character.
+     */
+    private void addKeyInfo(final int codePoint, final Keyboard keyboard,
+            final KeyDetector keyDetector) {
+        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;
+                final int[] codes = keyDetector.newCodeArray();
+                keyDetector.getNearbyCodes(x, y, codes);
+                add(codePoint, codes, x, y);
+                return;
+            }
+        }
+        add(codePoint, new int[] { 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,
+            final KeyDetector keyDetector) {
+        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, keyDetector);
+        }
+    }
+
+    /**
+     * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
+     */
+    public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
+        final KeyDetector keyDetector = new KeyDetector(0);
+        keyDetector.setKeyboard(keyboard, 0, 0);
+        keyDetector.setProximityCorrectionEnabled(true);
+        keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+        setComposingWord(word, keyboard, keyDetector);
     }
 
     /**
@@ -135,7 +245,7 @@
      * @param primaryCode the preferred character
      * @param codes array of codes based on distance from touch point
      */
-    private void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
+    private static 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];
@@ -147,27 +257,45 @@
      * Delete the last keystroke as a result of hitting backspace.
      */
     public void deleteLast() {
-        final int size = size();
+        final int size = mCodes.size();
         if (size > 0) {
-            final int lastPos = size - 1;
-            char lastChar = mTypedWord.charAt(lastPos);
-            mCodes.remove(lastPos);
-            mTypedWord.deleteCharAt(lastPos);
+            mCodes.remove(size - 1);
+            // 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--;
         }
-        if (size() == 0) {
+        // We may have deleted the last one.
+        if (0 == mCodes.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 +307,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 +326,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 +342,50 @@
     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 ArrayList<int[]> codes = mCodes;
+        final int[] xCoordinates = mXCoordinates;
+        final int[] yCoordinates = mYCoordinates;
+        mCodes = new ArrayList<int[]>(N);
+        mXCoordinates = new int[N];
+        mYCoordinates = new int[N];
+        final LastComposedWord lastComposedWord = new LastComposedWord(codes,
+                xCoordinates, yCoordinates, mTypedWord.toString(), committedWord, separatorCode);
+        if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
+                && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
+            lastComposedWord.deactivate();
+        }
+        mTypedWord.setLength(0);
+        mAutoCorrection = null;
+        return lastComposedWord;
+    }
+
+    public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
+        mCodes = lastComposedWord.mCodes;
+        mXCoordinates = lastComposedWord.mXCoordinates;
+        mYCoordinates = lastComposedWord.mYCoordinates;
+        mTypedWord.setLength(0);
+        mTypedWord.append(lastComposedWord.mTypedWord);
+        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..e14c71c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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 {
+    @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/java/src/com/android/inputmethod/latin/define/JniLibName.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java
new file mode 100644
index 0000000..3e94a3c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/define/JniLibName.java
@@ -0,0 +1,21 @@
+/*
+ * 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.define;
+
+public class JniLibName {
+    public static final String JNI_LIB_NAME = "jni_latinime";
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 095c2c5..35a5c0f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -17,7 +17,9 @@
 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;
@@ -25,25 +27,28 @@
 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 +56,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 +90,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 +203,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 +215,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 +225,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,7 +236,7 @@
 
         @Override
         synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
-                int dicTypeId, DataType dataType) {
+                int dicTypeId, int dataType) {
             final int positionIndex = ArraysCompatUtils.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.
@@ -175,7 +269,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 +292,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 +316,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 +326,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,16 +337,17 @@
                 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);
         }
     }
 
@@ -273,13 +368,15 @@
         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;
     }
@@ -295,9 +392,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 +413,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 +431,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 +450,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 +464,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 +516,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 +547,7 @@
             try {
                 final String text = textInfo.getText();
 
-                if (shouldFilterOut(text)) {
+                if (shouldFilterOut(text, mScript)) {
                     DictAndProximity dictInfo = null;
                     try {
                         dictInfo = mDictionaryPool.takeOrGetNull();
@@ -426,22 +565,12 @@
 
                 // 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 };
-                    } else {
-                        proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
-                                proximityIndex,
-                                proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
-                    }
-                    composer.add(character, proximities,
-                            WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+                for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
+                    composer.addKeyForSpellChecker(text.codePointAt(i), mScript);
                 }
 
                 final int capitalizeType = getCapitalizationType(text);
@@ -475,7 +604,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 +612,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..db35449 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -22,72 +22,158 @@
 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 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,
-
-        '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);
         }
     }
-    public static int getIndexOf(int characterCode) {
-        final Integer result = INDICES.get(characterCode);
+    final protected static int computeIndex(final int characterCode,
+            final TreeMap<Integer, Integer> indices) {
+        final Integer result = indices.get(characterCode);
         if (null == result) return -1;
         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 = {
+            '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,
+
+            '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,
+            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 {
+            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>();
+        final static int[] PROXIMITY = {
+            // TODO: This table is solely based on the keyboard layout. Consult with Russian
+            // speakers on commonly misspelled words/letters.
+            'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'к', 'у', 'в', 'а', 'п', 'е', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'е', 'к', 'а', 'п', 'р', 'н', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'н', 'е', 'п', 'р', 'о', 'г', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'г', 'н', 'р', 'о', 'л', 'ш', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ш', 'г', 'о', 'л', 'д', 'щ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'щ', 'ш', 'л', 'д', 'ж', 'з', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+            'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'а', 'у', 'к', 'е', 'в', 'п', 'ч', 'с', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'п', 'к', 'е', 'н', 'а', 'р', 'с', 'м', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'р', 'е', 'н', 'г', 'п', 'о', 'м', 'и', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'о', 'н', 'г', 'ш', 'р', 'л', 'и', 'т', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'л', 'г', 'ш', 'щ', 'о', 'д', 'т', 'ь', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'д', 'ш', 'щ', 'з', 'л', 'ж', 'ь', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+            'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'м', 'а', 'п', 'р', 'с', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'и', 'п', 'р', 'о', 'м', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'т', 'р', 'о', 'л', 'и', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 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);
+        }
+    }
+    public static int getIndexOfCodeForScript(final int characterCode, final int script) {
+        switch (script) {
+            case AndroidSpellCheckerService.SCRIPT_LATIN:
+                return Latin.getIndexOf(characterCode);
+            case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+                return Cyrillic.getIndexOf(characterCode);
+            default:
+                throw new RuntimeException("Wrong script supplied: " + script);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
similarity index 84%
rename from java/src/com/android/inputmethod/latin/MoreSuggestions.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 9a59ef2..c9c88fd 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -14,37 +14,38 @@
  * 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.LatinImeLogger;
+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 static final boolean DBG = LatinImeLogger.sDBG;
+
         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];
@@ -61,7 +62,7 @@
                 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(
@@ -71,7 +72,7 @@
                 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;
                     final int numColumn = pos - rowStartPos + 1;
@@ -176,9 +177,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 +190,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 +213,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 93%
rename from java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 8eab794..f11b1a4 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
@@ -55,17 +56,17 @@
     private 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,11 +141,6 @@
     }
 
     @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;
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 c25ecb3..d3c3afb 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;
@@ -58,7 +57,13 @@
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.PointerTracker;
+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.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.Utils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -73,7 +78,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 +96,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 +106,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 +120,6 @@
             }
         }
 
-        public void postHidePreview() {
-            cancelHidePreview();
-            sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW);
-        }
-
         public void cancelHidePreview() {
             removeMessages(MSG_HIDE_PREVIEW);
         }
@@ -141,7 +139,8 @@
         public final int mDividerWidth;
         public final int mSuggestionsStripHeight;
         public final int mSuggestionsCountInStrip;
-        public final int mMaxMoreSuggestionsRow;
+        public final int mMoreSuggestionsRowHeight;
+        private int mMaxMoreSuggestionsRow;
         public final float mMinMoreSuggestionsWidth;
         public final int mMoreSuggestionsBottomGap;
 
@@ -149,6 +148,7 @@
         private final List<View> mDividers;
         private final List<TextView> mInfos;
 
+        private final int mColorValidTypedWord;
         private final int mColorTypedWord;
         private final int mColorAutoCorrect;
         private final int mColorSuggested;
@@ -172,7 +172,6 @@
 
         public final TextView mWordToSaveView;
         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) {
@@ -193,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,
@@ -200,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(
@@ -224,11 +228,32 @@
             mCenterSuggestionIndex = mSuggestionsCountInStrip / 2;
             mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
                     R.dimen.more_suggestions_bottom_gap);
+            mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+                    R.dimen.more_suggestions_row_height);
 
             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);
+        }
+
+        public int getMaxMoreSuggestionsRow() {
+            return mMaxMoreSuggestionsRow;
+        }
+
+        private int getMoreSuggestionsHeight() {
+            return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+        }
+
+        public int setMoreSuggestionsHeight(int remainingHeight) {
+            final int currentHeight = getMoreSuggestionsHeight();
+            if (currentHeight <= remainingHeight) {
+                return currentHeight;
+            }
+
+            mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+                    / mMoreSuggestionsRowHeight;
+            final int newHeight = getMoreSuggestionsHeight();
+            return newHeight;
         }
 
         private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) {
@@ -258,10 +283,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;
 
@@ -278,10 +303,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) {
@@ -291,28 +316,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;
@@ -331,19 +359,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);
@@ -366,7 +394,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();
@@ -377,8 +405,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,11 +438,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++) {
@@ -423,8 +451,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.
@@ -433,8 +462,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);
@@ -445,7 +474,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;
@@ -464,13 +493,85 @@
             final TextView hintView = mHintToSaveView;
             hintView.setTextColor(mColorAutoCorrect);
             final int hintWidth = width - wordWidth;
-            final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint());
-            hintView.setText(mHintToSaveText);
+            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;
+            }
+        }
     }
 
     /**
@@ -548,98 +649,17 @@
         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());
+        mSuggestedWords = suggestedWords;
+        mParams.layout(mSuggestedWords, 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;
-        }
+    public int setMoreSuggestionsHeight(int remainingHeight) {
+        return mParams.setMoreSuggestionsHeight(remainingHeight);
     }
 
     public boolean isShowingAddToDictionaryHint() {
@@ -647,9 +667,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() {
@@ -661,7 +681,7 @@
     }
 
     public SuggestedWords getSuggestions() {
-        return mSuggestions;
+        return mSuggestedWords;
     }
 
     public void clear() {
@@ -675,34 +695,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 =
@@ -710,7 +704,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;
@@ -755,9 +749,9 @@
             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.mMaxMoreSuggestionsRow);
+                    params.getMaxMoreSuggestionsRow());
             mMoreSuggestionsView.setKeyboard(builder.build());
             container.measure(
                     ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -832,8 +826,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;
@@ -858,10 +851,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 24c0037..5053e7d 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -1,64 +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 ($(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
-
-ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
-    LOCAL_NDK_VERSION := 4
-    LOCAL_SDK_VERSION := 8
-endif
-
-LOCAL_MODULE := libjni_latinime
-
-LOCAL_MODULE_TAGS := optional
-
-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..ffaede3
--- /dev/null
+++ b/native/jni/Android.mk
@@ -0,0 +1,92 @@
+# 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)
+
+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))
+
+#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
+
+ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
+    LOCAL_NDK_VERSION := 4
+    LOCAL_SDK_VERSION := 8
+endif
+
+LOCAL_MODULE := libjni_latinime
+
+LOCAL_MODULE_TAGS := optional
+
+# For STL
+LOCAL_C_INCLUDES += external/stlport/stlport bionic
+LOCAL_SHARED_LIBRARIES += libstlport
+
+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)
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..f2878ee 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,75 @@
 
 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) {
     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
@@ -121,23 +123,23 @@
     }
     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,17 +153,17 @@
     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) {
     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,
@@ -175,19 +177,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 +230,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 +242,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;JJIIIII)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[IIII)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/src/additional_proximity_chars.cpp b/native/src/additional_proximity_chars.cpp
new file mode 100644
index 0000000..224f020
--- /dev/null
+++ b/native/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/src/additional_proximity_chars.h b/native/src/additional_proximity_chars.h
new file mode 100644
index 0000000..e0ecc0e
--- /dev/null
+++ b/native/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/src/basechars.cpp
similarity index 98%
rename from native/src/basechars.h
rename to native/src/basechars.cpp
index 3843e11..31f1e18 100644
--- a/native/src/basechars.h
+++ b/native/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/src/bigram_dictionary.cpp
index c340c6c..84048d7 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/src/bigram_dictionary.cpp
@@ -28,12 +28,12 @@
 BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength,
         int maxAlternatives, const bool isLatestDictVersion, const bool hasBigram,
         Dictionary *parentDictionary)
-    : DICT(dict + NEW_DICTIONARY_HEADER_SIZE), MAX_WORD_LENGTH(maxWordLength),
+    : DICT(dict), MAX_WORD_LENGTH(maxWordLength),
     MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion),
     HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) {
     if (DEBUG_DICT) {
-        LOGI("BigramDictionary - constructor");
-        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;
     }
@@ -134,7 +134,8 @@
         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);
         }
diff --git a/native/src/bigram_dictionary.h b/native/src/bigram_dictionary.h
index c07458a..585a186 100644
--- a/native/src/bigram_dictionary.h
+++ b/native/src/bigram_dictionary.h
@@ -21,14 +21,14 @@
 
 class Dictionary;
 class BigramDictionary {
-public:
+ public:
     BigramDictionary(const unsigned char *dict, int maxWordLength, int maxAlternatives,
             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);
     ~BigramDictionary();
-private:
+ private:
     bool addWordBigram(unsigned short *word, int length, int frequency);
     int getBigramAddress(int *pos, bool advance);
     int getBigramFreq(int *pos);
diff --git a/native/src/binary_format.h b/native/src/binary_format.h
index 6f65088..ab033ad 100644
--- a/native/src/binary_format.h
+++ b/native/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.h b/native/src/char_utils.h
index a69a35e..607dc51 100644
--- a/native/src/char_utils.h
+++ b/native/src/char_utils.h
@@ -19,8 +19,47 @@
 
 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/src/correction.cpp
index 27dc407..087219e 100644
--- a/native/src/correction.cpp
+++ b/native/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/src/correction.h b/native/src/correction.h
index d4e99f0..ee55c96 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_CORRECTION_H
 #define LATINIME_CORRECTION_H
 
+#include <assert.h>
 #include <stdint.h>
 #include "correction_state.h"
 
@@ -27,8 +28,7 @@
 class ProximityInfo;
 
 class Correction {
-
-public:
+ public:
     typedef enum {
         TRAVERSE_ALL_ON_TERMINAL,
         TRAVERSE_ALL_NOT_ON_TERMINAL,
@@ -37,6 +37,62 @@
         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);
@@ -44,11 +100,11 @@
 
     // TODO: remove
     void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
-            const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance);
+            const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
+            const bool doAutoCompletion, const int maxErrors);
     void checkState();
     bool initProcessState(const int index);
 
-    int getOutputIndex();
     int getInputIndex();
 
     virtual ~Correction();
@@ -73,9 +129,12 @@
 
     bool needsToPrune() const;
 
-    int getFreqForSplitTwoWords(
-            const int firstFreq, const int secondFreq, const unsigned short *word);
+    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);
 
@@ -94,21 +153,44 @@
     inline int getTreeParentIndex(const int index) const {
         return mCorrectionStates[index].mParentIndex;
     }
-private:
+
+    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 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 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;
@@ -116,6 +198,7 @@
     int mMissingSpacePos;
     int mTerminalInputIndex;
     int mTerminalOutputIndex;
+    int mMaxErrors;
 
     // The following arrays are state buffer.
     unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
@@ -146,17 +229,11 @@
 
     bool mMatching;
     bool mProximityMatching;
+    bool mAdditionalProximityMatching;
     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/correction_state.h b/native/src/correction_state.h
index c04146e..5b2cbd3 100644
--- a/native/src/correction_state.h
+++ b/native/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/src/debug.h
index 38b2f10..b13052c 100644
--- a/native/src/debug.h
+++ b/native/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/src/defines.h
index ef1beb9..b0dbffa 100644
--- a/native/src/defines.h
+++ b/native/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 ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO -4
 #define NOT_A_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
 
+#define FIRST_WORD_INDEX 0
+
 // 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
 
 // 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/src/dictionary.cpp
index a49769b..8e252f7 100644
--- a/native/src/dictionary.cpp
+++ b/native/src/dictionary.cpp
@@ -19,6 +19,7 @@
 
 #define LOG_TAG "LatinIME: dictionary.cpp"
 
+#include "binary_format.h"
 #include "dictionary.h"
 
 namespace latinime {
@@ -33,26 +34,28 @@
     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, maxAlternatives, IS_LATEST_DICT_VERSION);
+    mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength, maxAlternatives,
+            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/src/dictionary.h b/native/src/dictionary.h
index d5de008..90d7148 100644
--- a/native/src/dictionary.h
+++ b/native/src/dictionary.h
@@ -17,22 +17,25 @@
 #ifndef LATINIME_DICTIONARY_H
 #define LATINIME_DICTIONARY_H
 
-#include "basechars.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:
+ 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,
+        return mUnigramDictionary->getSuggestions(proximityInfo, mWordsPriorityQueuePool,
+                mCorrection, xcoordinates, ycoordinates, codes,
                 codesSize, flags, outWords, frequencies);
     }
 
@@ -53,19 +56,9 @@
 
     // 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:
+ private:
     bool hasBigram();
 
     const unsigned char *mDict;
@@ -79,60 +72,12 @@
     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 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;
@@ -140,35 +85,6 @@
         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
index 763a3a1..dd60ed6 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -16,10 +16,11 @@
 
 #include <assert.h>
 #include <stdio.h>
-#include <string.h>
+#include <string>
 
 #define LOG_TAG "LatinIME: proximity_info.cpp"
 
+#include "additional_proximity_chars.h"
 #include "dictionary.h"
 #include "proximity_info.h"
 
@@ -33,26 +34,29 @@
     }
 }
 
-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,
+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),
-          mInputXCoordinates(NULL), mInputYCoordinates(NULL),
+          mLocaleStr(localeStr),
+          mInputXCoordinates(0), mInputYCoordinates(0),
           mTouchPositionCorrectionEnabled(false) {
     const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
-    mProximityCharsArray = new uint32_t[proximityGridLength];
+    mProximityCharsArray = new int32_t[proximityGridLength];
     if (DEBUG_PROXIMITY_INFO) {
-        LOGI("Create proximity info array %d", proximityGridLength);
+        AKLOGI("Create proximity info array %d", proximityGridLength);
     }
     memcpy(mProximityCharsArray, proximityCharsArray,
             proximityGridLength * sizeof(mProximityCharsArray[0]));
@@ -100,13 +104,21 @@
 }
 
 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) {
-        LOGI("hasSpaceProximity: index %d", startIndex);
+        AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
     }
     for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
         if (DEBUG_PROXIMITY_INFO) {
-            LOGI("Index: %d", mProximityCharsArray[startIndex + i]);
+            AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
         }
         if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
             return true;
@@ -115,6 +127,86 @@
     return false;
 }
 
+bool ProximityInfo::isOnKey(const int keyId, const int x, const int y) {
+    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 int left = mKeyXCoordinates[keyId];
+    const int top = mKeyYCoordinates[keyId];
+    const int right = left + mKeyWidths[keyId] + 1;
+    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) {
+    int insertPos = 0;
+    inputCodes[insertPos++] = primaryKey;
+    const int startIndex = getStartIndexFromCoordinates(x, y);
+    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+        const int32_t c = mProximityCharsArray[startIndex + i];
+        if (c < KEYCODE_SPACE || c == primaryKey) {
+            continue;
+        }
+        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;
+            }
+        }
+    }
+    inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+    if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+        if (DEBUG_DICT) {
+            assert(false);
+        }
+        return;
+    }
+
+    const int additionalProximitySize =
+            AdditionalProximityChars::getAdditionalCharsSize(&mLocaleStr, primaryKey);
+    if (additionalProximitySize > 0) {
+        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
+    inputCodes[insertPos] = 0;
+}
+
 // TODO: Calculate nearby codes here.
 void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength,
         const int* xCoordinates, const int* yCoordinates) {
@@ -128,8 +220,30 @@
         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 int keyIndex = getKeyIndex(currentChar);
@@ -142,6 +256,9 @@
                         ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO
                         : PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
             }
+            if (DEBUG_PROXIMITY_CHARS) {
+                AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+            }
         }
     }
 }
@@ -157,6 +274,9 @@
     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;
@@ -167,7 +287,7 @@
         // We do not have the coordinate data
         return NOT_A_INDEX;
     }
-    const unsigned short baseLowerC = Dictionary::toBaseLowerCase(c);
+    const unsigned short baseLowerC = toBaseLowerCase(c);
     if (baseLowerC > MAX_CHAR_CODE) {
         return NOT_A_INDEX;
     }
@@ -232,7 +352,7 @@
         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);
+    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.
@@ -245,12 +365,13 @@
     // 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)
+    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] > 0) {
+    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) {
@@ -260,6 +381,21 @@
         }
         ++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;
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 35e354c..1a47aff 100644
--- a/native/src/proximity_info.h
+++ b/native/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 int *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,7 +72,7 @@
         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
@@ -85,21 +89,27 @@
         // 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);
+    int squaredDistanceToEdge(const int keyId, const int x, const int y);
+    void calculateNearbyKeyCodes(
+            const int x, const int y, const int32_t primaryKey, int *inputCodes);
 
     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 std::string mLocaleStr;
     const int *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/src/terminal_attributes.h b/native/src/terminal_attributes.h
new file mode 100644
index 0000000..1f98159
--- /dev/null
+++ b/native/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/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 8eb5a97..6c2c892 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -25,6 +25,7 @@
 #include "unigram_dictionary.h"
 
 #include "binary_format.h"
+#include "terminal_attributes.h"
 
 namespace latinime {
 
@@ -37,8 +38,7 @@
 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),
+    : DICT_ROOT(streamStart), 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.
@@ -46,21 +46,25 @@
     BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(int)),
     MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
     if (DEBUG_DICT) {
-        LOGI("UnigramDictionary - constructor");
+        AKLOGI("UnigramDictionary - constructor");
     }
-    mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
 }
 
 UnigramDictionary::~UnigramDictionary() {
-    delete mCorrection;
 }
 
-static inline unsigned int getCodesBufferSize(const int* codes, const int codesSize,
+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 {
+// 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);
+}
+
+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;
@@ -86,12 +90,17 @@
 // 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) {
+        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 int startIndex = (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS;
     if (currentDepth < MAX_UMLAUT_SEARCH_DEPTH) {
         for (int i = 0; i < codesRemain; ++i) {
+            xCoordinatesBuffer[startIndex + i] = xcoordinates[codesBufferSize - codesRemain + i];
+            yCoordinatesBuffer[startIndex + i] = ycoordinates[codesBufferSize - codesRemain + i];
             if (isDigraph(codesSrc, i, codesRemain)) {
                 // Found a digraph. We will try both spellings. eg. the word is "pruefen"
 
@@ -103,10 +112,10 @@
                 ++i;
                 memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
-                        codesBuffer, codesBufferSize, flags,
+                        codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
                         codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
-                        currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, outWords,
-                        frequencies);
+                        currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, correction,
+                        queuePool);
 
                 // Copy the second char of the digraph in place, then continue processing on
                 // the remaining part of the word.
@@ -114,9 +123,9 @@
                 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);
+                        codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
+                        codesSrc + i * MAX_PROXIMITY_CHARS, codesRemain - i, currentDepth + 1,
+                        codesDest + i * MAX_PROXIMITY_CHARS, correction, queuePool);
                 return;
             }
         }
@@ -128,45 +137,60 @@
     // 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)
+    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, xcoordinates, ycoordinates, codesBuffer,
-            (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies,
-            flags);
+    getWordSuggestions(proximityInfo, xCoordinatesBuffer, yCoordinatesBuffer, codesBuffer,
+            startIndex + codesRemain, flags, correction,
+            queuePool);
 }
 
-int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
+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, MAX_PROXIMITY_CHARS)];
+        int xCoordinatesBuffer[codesSize];
+        int yCoordinatesBuffer[codesSize];
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
+                xCoordinatesBuffer, yCoordinatesBuffer,
+                codesSize, flags, codes, codesSize, 0, codesBuffer, masterCorrection, queuePool);
     } else { // Normal processing
-        getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
-                outWords, frequencies, flags);
+        getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize, flags,
+                masterCorrection, queuePool);
     }
 
     PROF_START(20);
-    // Get the word count
-    int suggestedWordsCount = 0;
-    while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
-        suggestedWordsCount++;
+    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) {
-        LOGI("Returning %d words", suggestedWordsCount);
+        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) {
-#ifdef FLAG_DBG
-            short unsigned int* w = mOutputChars + j * MAX_WORD_LENGTH;
+            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];
-            LOGI("%s %i", s, mFrequencies[j]);
-#endif
+            AKLOGI("%s %i", s, frequencies[j]);
         }
     }
     PROF_END(20);
@@ -175,23 +199,18 @@
 }
 
 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) {
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const int inputLength, const int flags, Correction *correction,
+        WordsPriorityQueuePool *queuePool) {
 
     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);
+    const bool useFullEditDistance = USE_FULL_EDIT_DISTANCE & flags;
+    getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, useFullEditDistance,
+            inputLength, correction, queuePool);
     PROF_END(1);
 
     PROF_START(2);
@@ -203,250 +222,369 @@
     PROF_END(3);
 
     PROF_START(4);
-    // Note: This line is intentionally left blank
+    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);
-    // 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);
-        }
+    // 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);
-    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);
+    // 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);
             }
         }
     }
-    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) {
+        const int *yCoordinates, const int *codes, const int inputLength, Correction *correction) {
     if (DEBUG_DICT) {
-        LOGI("initSuggest");
+        AKLOGI("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;
+    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::getSuggestionCandidates(const bool useFullEditDistance) {
+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
-    mCorrection->setCorrectionParams(0, 0, 0,
-            -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance);
+    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 = Dictionary::getCount(DICT_ROOT, &rootPosition);
+    int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
     int outputIndex = 0;
 
-    mCorrection->initCorrectionState(rootPosition, childCount, (mInputLength <= 0));
+    correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0));
 
     // Depth first search
     while (outputIndex >= 0) {
-        if (mCorrection->initProcessState(outputIndex)) {
-            int siblingPos = mCorrection->getTreeSiblingPos(outputIndex);
+        if (correction->initProcessState(outputIndex)) {
+            int siblingPos = correction->getTreeSiblingPos(outputIndex);
             int firstChildPos;
 
             const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
-                    mCorrection, &childCount, &firstChildPos, &siblingPos);
+                    correction, &childCount, &firstChildPos, &siblingPos, queuePool,
+                    currentWordIndex);
             // Update next sibling pos
-            mCorrection->setTreeSiblingPos(outputIndex, siblingPos);
+            correction->setTreeSiblingPos(outputIndex, siblingPos);
 
             if (needsToTraverseChildrenNodes) {
                 // Goes to child node
-                outputIndex = mCorrection->goDownTree(outputIndex, childCount, firstChildPos);
+                outputIndex = correction->goDownTree(outputIndex, childCount, firstChildPos);
             }
         } else {
             // Goes to parent sibling node
-            outputIndex = mCorrection->getTreeParentIndex(outputIndex);
+            outputIndex = correction->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);
-}
+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;
 
-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);
+
+    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);
     }
 }
 
-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);
+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);
 
-    if (inputLength >= MAX_WORD_LENGTH) return;
-    if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
-            || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength)
+    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 * MAX_PROXIMITY_CHARS, 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;
+        }
 
-    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_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) {
-        LOGI("First freq: %d", firstFreq);
+        // MAX_PROXIMITY_CHARS_SIZE in ProximityInfo.java should be 16
+        assert(MAX_PROXIMITY_CHARS == 16);
     }
-    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];
+        AKLOGI("--- Suggest multiple words");
     }
 
-    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;
+    // 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, unsigned short *word) {
+        const int inputLength, ProximityInfo *proximityInfo, unsigned short *word) {
     uint16_t inWord[inputLength];
 
     for (int i = 0; i < inputLength; ++i) {
-        inWord[i] = (uint16_t)mProximityInfo->getPrimaryCharAt(startInputIndex + i);
+        inWord[i] = (uint16_t)proximityInfo->getPrimaryCharAt(startInputIndex + i);
     }
     return getMostFrequentWordLikeInner(inWord, inputLength, word);
 }
@@ -470,8 +608,8 @@
     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]);
+    int32_t baseChar = toBaseLowerCase(character);
+    const uint16_t wChar = toBaseLowerCase(inWord[startInputIndex]);
 
     if (baseChar != wChar) {
         *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos;
@@ -483,8 +621,8 @@
     if (hasMultipleChars) {
         character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
         while (NOT_A_CHARACTER != character) {
-            baseChar = Dictionary::toBaseLowerCase(character);
-            if (Dictionary::toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
+            baseChar = toBaseLowerCase(character);
+            if (toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
                 *outPos = BinaryFormat::skipOtherCharacters(root, pos);
                 *outInputIndex = startInputIndex;
                 return false;
@@ -521,9 +659,10 @@
     int maxFreq = -1;
     const uint8_t* const root = DICT_ROOT;
 
-    mStackChildCount[0] = root[0];
+    int startPos = 0;
+    mStackChildCount[0] = BinaryFormat::getGroupCountAndForwardPointer(root, &startPos);
     mStackInputIndex[0] = 0;
-    mStackSiblingPos[0] = 1;
+    mStackSiblingPos[0] = startPos;
     while (depth >= 0) {
         const int charGroupCount = mStackChildCount[depth];
         int pos = mStackSiblingPos[depth];
@@ -597,7 +736,8 @@
 // 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) {
+        int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
+        const int currentWordIndex) {
     if (DEBUG_DICT) {
         correction->checkState();
     }
@@ -648,7 +788,7 @@
         if (stateType == Correction::TRAVERSE_ALL_ON_TERMINAL
                 || stateType == Correction::ON_TERMINAL) {
             needsToInvokeOnTerminal = true;
-        } else if (stateType == Correction::UNRELATED) {
+        } 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
@@ -672,12 +812,14 @@
     } 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);
-        }
+        // 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.
@@ -702,7 +844,7 @@
             *nextSiblingPosition =
                     BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
             if (DEBUG_DICT_FULL) {
-                LOGI("Traversing was pruned.");
+                AKLOGI("Traversing was pruned.");
             }
             return false;
         }
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index ef9709a..12b68d7 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -22,17 +22,14 @@
 #include "correction_state.h"
 #include "defines.h"
 #include "proximity_info.h"
-
-#ifndef NULL
-#define NULL 0
-#endif
+#include "words_priority_queue.h"
+#include "words_priority_queue_pool.h"
 
 namespace latinime {
 
+class TerminalAttributes;
 class UnigramDictionary {
-
-public:
-
+ 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;
@@ -46,8 +43,14 @@
     // 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
@@ -64,47 +67,74 @@
     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, 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,
+    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:
-
+ 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;
+            const int *ycoordinates, const int *codes, const int inputLength,
+            const int flags, Correction *correction, WordsPriorityQueuePool *queuePool);
+    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);
+        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);
     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);
+            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);
+    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,
-            unsigned short *word);
+            ProximityInfo *proximityInfo, unsigned short *word);
     int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,
-            short unsigned int* outWord);
+            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;
@@ -127,14 +157,8 @@
     };
     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];
-
+    // 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
diff --git a/native/src/words_priority_queue.h b/native/src/words_priority_queue.h
new file mode 100644
index 0000000..249962e
--- /dev/null
+++ b/native/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/src/words_priority_queue_pool.h b/native/src/words_priority_queue_pool.h
new file mode 100644
index 0000000..5b50e8f
--- /dev/null
+++ b/native/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/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/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/ArbitrarySubtype.java b/tests/src/com/android/inputmethod/latin/ArbitrarySubtype.java
new file mode 100644
index 0000000..1e3482c
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/ArbitrarySubtype.java
@@ -0,0 +1,54 @@
+/*
+ * 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.compat.InputMethodSubtypeCompatWrapper;
+
+public class ArbitrarySubtype extends InputMethodSubtypeCompatWrapper {
+    final String mLocale;
+    final String mExtraValue;
+
+    public ArbitrarySubtype(final String locale, final String extraValue) {
+        super(locale);
+        mLocale = locale;
+        mExtraValue = extraValue;
+    }
+
+    public String getLocale() {
+        return mLocale;
+    }
+
+    public String getExtraValue() {
+        return mExtraValue;
+    }
+
+    public String getMode() {
+        return "keyboard";
+    }
+
+    public String getExtraValueOf(final String key) {
+        if (LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE.equals(key)) {
+            return "";
+        } else {
+            return null;
+        }
+    }
+
+    public boolean containsExtraValueKey(final String key) {
+        return LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE.equals(key);
+    }
+}
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/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
new file mode 100644
index 0000000..595fe5b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -0,0 +1,684 @@
+/*
+ * 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.Intent;
+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.util.Log;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info
+import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class InputLogicTests extends ServiceTestCase<LatinIME> {
+
+    private static final String PREF_DEBUG_MODE = "debug_mode";
+
+    private LatinIME mLatinIME;
+    private TextView mTextView;
+    private InputConnection mInputConnection;
+
+    public InputLogicTests() {
+        super(LatinIME.class);
+    }
+
+    // returns the previous setting value
+    private 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);
+        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;
+        changeLanguage("en_US");
+    }
+
+    // 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.
+    private 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.
+    private 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);
+        mLatinIME.onCodeInput(codePoint,
+                KeyboardActionListener.SPELL_CHECKER_COORDINATE,
+                KeyboardActionListener.SPELL_CHECKER_COORDINATE);
+        //mLatinIME.onReleaseKey(codePoint, false);
+    }
+
+    private void type(final String stringToType) {
+        for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) {
+            type(stringToType.codePointAt(i));
+        }
+    }
+
+    private 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");
+        }
+    }
+
+    private void changeLanguage(final String locale) {
+        SubtypeSwitcher.getInstance().updateSubtype(
+                new ArbitrarySubtype(locale, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE));
+        waitForDictionaryToBeLoaded();
+    }
+
+
+    // Helper to avoid writing the try{}catch block each time
+    private static void sleep(final int milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {}
+    }
+
+    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 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 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 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 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());
+    }
+
+    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 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 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 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());
+    }
+
+    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());
+    }
+
+    // A helper class to ease span tests
+    private 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());
+        }
+    }
+
+    static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; // The message is posted with a 100 ms delay
+    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);
+    }
+    // TODO: Add some tests for non-BMP characters
+}
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index fec3e8e..7925d1a 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,29 @@
                 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());
+            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/src/com/android/inputmethod/latin/BinaryDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
index 92f402d..485dba1 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
@@ -26,6 +26,7 @@
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.TreeMap;
 
@@ -44,8 +45,9 @@
      * 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
+     *   | is shortcut only ?          1 bit, 1 = yes, 0 = no   : FLAG_IS_SHORTCUT_ONLY
      *
      * c | IF FLAG_HAS_MULTIPLE_CHARS
      * h |   char, char, char, char    n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers
@@ -71,6 +73,8 @@
      * d
      * dress
      *
+     *   | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
+     *   | shortcut targets address list
      *   | IF FLAG_IS_TERMINAL && FLAG_HAS_BIGRAMS
      *   | bigrams address list
      *
@@ -107,9 +111,13 @@
      *
      */
 
-    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,7 +134,9 @@
     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_IS_SHORTCUT_ONLY = 0x02;
 
     private static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
     private static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
@@ -138,7 +148,6 @@
 
     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;
@@ -149,9 +158,8 @@
     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;
 
@@ -261,6 +269,31 @@
     }
 
     /**
+     * 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 maximum size of a CharGroup, assuming 3-byte addresses for everything.
      *
      * @param group the CharGroup to compute the size of.
@@ -271,10 +304,13 @@
         // If terminal, one byte for the frequency
         if (group.isTerminal()) size += GROUP_FREQUENCY_SIZE;
         size += GROUP_MAX_ADDRESS_SIZE; // For children address
+        if (null != group.mShortcutTargets) {
+            size += (GROUP_ATTRIBUTE_FLAGS_SIZE + GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE)
+                    * group.mShortcutTargets.size();
+        }
         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 +322,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;
@@ -303,6 +339,13 @@
     }
 
     /**
+     * Helper method to find out if a character info is a shortcut only.
+     */
+    private static boolean isShortcutOnly(final CharGroupInfo info) {
+        return 0 != (info.mFlags & FLAG_IS_SHORTCUT_ONLY);
+    }
+
+    /**
      * Compute the size, in bytes, that an address will occupy.
      *
      * This can be used either for children addresses (which are always positive) or for
@@ -378,7 +421,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 +430,15 @@
                 final int offset = group.mChildren.mCachedAddress - offsetBasePoint;
                 groupSize += getByteSize(offset);
             }
+            if (null != group.mShortcutTargets) {
+                for (WeightedString target : group.mShortcutTargets) {
+                    final int offsetBasePoint = groupSize + node.mCachedAddress + size
+                            + GROUP_FLAGS_SIZE;
+                    final int addressOfTarget = findAddressOfWord(dict, target.mWord);
+                    final int offset = addressOfTarget - offsetBasePoint;
+                    groupSize += getByteSize(offset) + GROUP_FLAGS_SIZE;
+                }
+            }
             if (null != group.mBigrams) {
                 for (WeightedString bigram : group.mBigrams) {
                     final int offsetBasePoint = groupSize + node.mCachedAddress + size
@@ -412,12 +464,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;
@@ -545,7 +598,21 @@
                  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;
+        }
+        if (group.mIsShortcutOnly) {
+            flags |= FLAG_IS_SHORTCUT_ONLY;
+        }
         return flags;
     }
 
@@ -592,13 +659,20 @@
     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");
@@ -624,20 +698,36 @@
             index += shift;
             groupAddress += shift;
 
+            // Write shortcuts
+            if (null != group.mShortcutTargets) {
+                final Iterator shortcutIterator = group.mShortcutTargets.iterator();
+                while (shortcutIterator.hasNext()) {
+                    final WeightedString target = (WeightedString)shortcutIterator.next();
+                    final int addressOfTarget = findAddressOfWord(dict, target.mWord);
+                    ++groupAddress;
+                    final int offset = addressOfTarget - groupAddress;
+                    int shortcutFlags = makeAttributeFlags(shortcutIterator.hasNext(), offset,
+                            target.mFrequency);
+                    buffer[index++] = (byte)shortcutFlags;
+                    final int shortcutShift = writeVariableAddress(buffer, index, Math.abs(offset));
+                    index += shortcutShift;
+                    groupAddress += shortcutShift;
+                }
+            }
             // 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 +801,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 +817,42 @@
         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);
-
-        // Should we include the locale and title of the dictionary ?
+        if (version >= FIRST_VERSION_WITH_HEADER_SIZE) {
+            final int headerSizeOffset = index;
+            index += 4; // Size of the header size
+            // TODO: Write out the header contents here.
+            // 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 +932,43 @@
             childrenAddress = NO_CHILDREN_ADDRESS;
             break;
         }
+        ArrayList<PendingAttribute> shortcutTargets = null;
+        if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
+            shortcutTargets = new ArrayList<PendingAttribute>();
+            while (true) {
+                final int targetFlags = source.readUnsignedByte();
+                ++addressPointer;
+                final int sign = 0 == (targetFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
+                int targetAddress = addressPointer;
+                switch (targetFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
+                case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+                    targetAddress += sign * source.readUnsignedByte();
+                    addressPointer += 1;
+                    break;
+                case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+                    targetAddress += sign * source.readUnsignedShort();
+                    addressPointer += 2;
+                    break;
+                case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+                    final int offset = ((source.readUnsignedByte() << 16)
+                            + source.readUnsignedShort());
+                    targetAddress += sign * offset;
+                    addressPointer += 3;
+                    break;
+                default:
+                    throw new RuntimeException("Has shortcut targets with no address");
+                }
+                shortcutTargets.add(new PendingAttribute(targetFlags & FLAG_ATTRIBUTE_FREQUENCY,
+                        targetAddress));
+                if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
+            }
+        }
         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 +987,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 +1024,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 +1081,19 @@
             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 = null;
+            if (null != info.mShortcutTargets) {
+                shortcutTargets = new ArrayList<WeightedString>();
+                for (PendingAttribute target : info.mShortcutTargets) {
+                    final String word = getWordAtAddress(source, headerSize, target.mAddress);
+                    shortcutTargets.add(new WeightedString(word, target.mFrequency));
+                }
+            }
             ArrayList<WeightedString> bigrams = null;
             if (null != info.mBigrams) {
                 bigrams = new ArrayList<WeightedString>();
@@ -942,11 +1111,12 @@
                     source.seek(currentPosition);
                 }
                 nodeContents.add(
-                        new CharGroup(info.mCharacters, bigrams, info.mFrequency,
-                        children));
+                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
+                                children, isShortcutOnly(info)));
             } else {
                 nodeContents.add(
-                        new CharGroup(info.mCharacters, bigrams, info.mFrequency));
+                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
+                                isShortcutOnly(info)));
             }
             groupOffset = info.mEndAddress;
         }
@@ -957,6 +1127,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 +1148,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,7 +1161,16 @@
         // Read options
         source.readUnsignedShort();
 
-        long headerSize = source.getFilePointer();
+        final long headerSize;
+        if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
+            headerSize = source.getFilePointer();
+        } else {
+            headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
+                    + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
+            // read the header body
+            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);
@@ -996,7 +1179,7 @@
                 new FusionDictionary.DictionaryOptions());
         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 +1194,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/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
index 6badfd1..759cd45 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
@@ -29,10 +29,12 @@
     public final int[] mCharacters;
     public final int mFrequency;
     public final int mChildrenAddress;
+    public final ArrayList<PendingAttribute> 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<PendingAttribute> shortcutTargets,
             final ArrayList<PendingAttribute> bigrams) {
         mOriginalAddress = originalAddress;
         mEndAddress = endAddress;
@@ -40,6 +42,7 @@
         mCharacters = characters;
         mFrequency = frequency;
         mChildrenAddress = childrenAddress;
+        mShortcutTargets = shortcutTargets;
         mBigrams = bigrams;
     }
 }
diff --git a/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java b/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
index 1ba0107..fbfa424 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
@@ -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/FusionDictionary.java b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
index f6220ee..08143d3 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
@@ -68,7 +68,7 @@
     }
 
     /**
-     * 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 +82,39 @@
     public static class CharGroup {
         public static final int NOT_A_TERMINAL = -1;
         final int mChars[];
+        final ArrayList<WeightedString> mShortcutTargets;
         final ArrayList<WeightedString> mBigrams;
         final int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
+        final 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;
         }
@@ -150,13 +164,31 @@
     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 +197,19 @@
      *
      * @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) {
-        if (null != bigrams) {
-            for (WeightedString bigram : bigrams) {
-                final CharGroup t = findWordInTree(mRoot, bigram.mWord);
-                if (null == t) {
-                    add(getCodePoints(bigram.mWord), 0, null);
-                }
-            }
+    public void add(final String word, final int frequency,
+            final ArrayList<WeightedString> shortcutTargets,
+            final ArrayList<WeightedString> bigrams) {
+        if (null != shortcutTargets) {
+            addNeutralWords(shortcutTargets);
         }
-        add(getCodePoints(word), frequency, bigrams);
+        if (null != bigrams) {
+            addNeutralWords(bigrams);
+        }
+        add(getCodePoints(word), frequency, shortcutTargets, bigrams, false /* isShortcutOnly */);
     }
 
     /**
@@ -198,16 +231,37 @@
     }
 
     /**
+     * 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");
+        }
+        addNeutralWords(shortcutTargets);
+        add(getCodePoints(word), frequency, shortcutTargets, null, true /* isShortcutOnly */);
+    }
+
+    /**
      * 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 +285,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 {
@@ -245,7 +300,8 @@
                                 + new String(word, 0, word.length));
                     } else {
                         final CharGroup newNode = new CharGroup(currentGroup.mChars,
-                                bigrams, frequency, currentGroup.mChildren);
+                                shortcutTargets, bigrams, frequency, currentGroup.mChildren,
+                                isShortcutOnly);
                         currentNode.mData.set(nodeIndex, newNode);
                         checkStack(currentNode);
                     }
@@ -254,13 +310,13 @@
                     // 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.
+                    // Exact same word. Check the frequency is 0 or NOT_A_TERMINAL, and update.
                     if (0 != frequency) {
                         if (0 < currentGroup.mFrequency) {
                             throw new RuntimeException("This word already exists with frequency "
@@ -268,7 +324,9 @@
                                     + new String(word, 0, word.length));
                         }
                         final CharGroup newGroup = new CharGroup(word,
-                                currentGroup.mBigrams, frequency, currentGroup.mChildren);
+                                currentGroup.mShortcutTargets, currentGroup.mBigrams,
+                                frequency, currentGroup.mChildren,
+                                currentGroup.mIsShortcutOnly && isShortcutOnly);
                         currentNode.mData.set(nodeIndex, newGroup);
                     }
                 } else {
@@ -277,22 +335,27 @@
                     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);
@@ -355,7 +418,8 @@
      */
     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 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 +463,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 +647,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/PendingAttribute.java b/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
index e502021..cf66eef 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
@@ -19,7 +19,7 @@
 /**
  * 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/Word.java b/tools/makedict/src/com/android/inputmethod/latin/Word.java
index 916165a..cf6116f 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/Word.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/Word.java
@@ -28,12 +28,18 @@
 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) {
+    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;
     }
 
     /**
@@ -60,6 +66,7 @@
         if (!(o instanceof Word)) return false;
         Word w = (Word)o;
         return mFrequency == w.mFrequency && mWord.equals(w.mWord)
+                && mShortcutTargets.equals(w.mShortcutTargets)
                 && mBigrams.equals(w.mBigrams);
     }
 }
diff --git a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
index 35a7b51..77c5366 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
@@ -42,8 +42,12 @@
 
     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.
@@ -61,6 +65,7 @@
         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;
 
         /**
@@ -69,9 +74,11 @@
          * @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) {
+        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;
@@ -107,47 +114,96 @@
         @Override
         public void endElement(String uri, String localName, String qName) {
             if (WORD == mState) {
-                mDictionary.add(mWord, mFreq, mBigramsMap.get(mWord));
+                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 DefaultHandler {
+    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";
 
-        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);
-            }
+            super(BIGRAM_W1_TAG, BIGRAM_W1_ATTRIBUTE, BIGRAM_W2_TAG, BIGRAM_W2_ATTRIBUTE,
+                    BIGRAM_FREQ_ATTRIBUTE);
         }
 
         public HashMap<String, ArrayList<WeightedString>> getBigramMap() {
-            return mBigramsMap;
+            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();
         }
     }
 
@@ -158,9 +214,12 @@
      * 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(InputStream unigrams, InputStream bigrams)
+    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);
@@ -168,10 +227,23 @@
         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, bigramHandler.getBigramMap());
+                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;
     }
 
@@ -204,9 +276,20 @@
         }
         // TODO: use an XMLSerializer if this gets big
         destination.write("<wordlist format=\"2\">\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 + "\">");
+                    + 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) {
diff --git a/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java b/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
index 79cf14b..6ac046b 100644
--- a/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
+++ b/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
@@ -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()) {