Merge 2577fca1

Change-Id: Ie2c9f6c2eafb59dff95db8954481ce49c87a6d44
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..0aac3f0 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)
 
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..caf1ea1 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -35,6 +35,6 @@
   *;
 }
 
--keep class com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder$MiniKeyboardParams {
+-keep class com.android.inputmethod.keyboard.MiniKeyboard$Builder$MiniKeyboardParams {
   <init>(...);
 }
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 2e0cddc..b9451f8 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -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/more_suggestions.xml b/java/res/layout/more_suggestions.xml
index 6aa82e1..1e59b89 100644
--- a/java/res/layout/more_suggestions.xml
+++ b/java/res/layout/more_suggestions.xml
@@ -24,7 +24,7 @@
         android:orientation="horizontal"
         style="?attr/miniKeyboardPanelStyle"
         >
-    <com.android.inputmethod.latin.MoreSuggestionsView
+    <com.android.inputmethod.latin.suggestions.MoreSuggestionsView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
             android:id="@+id/more_suggestions_view"
             android:layout_alignParentBottom="true"
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 36c9d4b..e482d13 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropsies"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-korrigering"</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>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index d5280bc..288854d 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"ግቤት አማራጮች"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"የAndroid ማስተካከያ"</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>
diff --git a/java/res/values-ar/donottranslate-more-keys.xml b/java/res/values-ar/donottranslate-more-keys.xml
index cde6860..e49677a 100644
--- a/java/res/values-ar/donottranslate-more-keys.xml
+++ b/java/res/values-ar/donottranslate-more-keys.xml
@@ -70,10 +70,8 @@
     <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_f1_settings">\\,,\@icon/settingsKey|\@integer/key_settings</string>
+    <string name="more_keys_for_f1_navigate">\\,,\@icon/tabKey|\@integer/key_tab</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>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index dca7365..1be9d01 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"خيارات الإرسال"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"تصحيح Android"</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>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/values-be/donottranslate-more-keys.xml
similarity index 75%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/values-be/donottranslate-more-keys.xml
index d5fd8ef..28264c4 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/values-be/donottranslate-more-keys.xml
@@ -17,11 +17,7 @@
 ** 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>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="keylabel_for_slavic_shcha">ў</string>
+    <string name="keylabel_for_slavic_i">i</string>
+</resources>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 30fe132..41ef97a 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Корекция на Android"</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>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index a58d8fa..5dc3805 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index d57ccaa..bc31076 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-da/donottranslate-more-keys.xml b/java/res/values-da/donottranslate-more-keys.xml
index 12c1ebf..bed034d 100644
--- a/java/res/values-da/donottranslate-more-keys.xml
+++ b/java/res/values-da/donottranslate-more-keys.xml
@@ -28,6 +28,7 @@
     <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_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>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index b91acaa..7ae7b7e 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index d329f32..1a4b36b 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 6fe191e..f19699b 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Επιλογές εισόδου"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Διόρθωση Android"</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>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index a7d5086..f8cf4dc 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android correction"</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>
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..a400971 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index fcd65e1..bd8bf01 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
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..1011347
--- /dev/null
+++ b/java/res/values-et/donottranslate-more-keys.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.
+*/
+-->
+<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_k">ķ</string>
+    <string name="more_keys_for_l">ļ,ł,ĺ,ľ</string>
+    <string name="more_keys_for_g">ģ,ğ</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>
+</resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 7d8c1e9..7825531 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"گزینه های ورودی"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"تصحیح Android"</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>
diff --git a/java/res/values-fi/donottranslate-more-keys.xml b/java/res/values-fi/donottranslate-more-keys.xml
index df67c69..e1dfa2b 100644
--- a/java/res/values-fi/donottranslate-more-keys.xml
+++ b/java/res/values-fi/donottranslate-more-keys.xml
@@ -23,6 +23,7 @@
     <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_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>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index c77cd81..de216af 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 7b89edd..70e2772 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index de95ab8..9397e17 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"इनपुट विकल्‍प"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android correction"</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>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 30c20b3..c7e0987 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
@@ -35,7 +35,7 @@
     <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="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>
@@ -52,7 +52,7 @@
     <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>
@@ -132,7 +132,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>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 180b6fc..e864c67 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index a023b64..03f2ada 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opsi masukan"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Koreksi android"</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>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 58c0e2c..a820635 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opzioni inserimento"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Correzione Android"</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>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 292517c..c2743d9 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"אפשרויות קלט"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"תיקון Android"</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>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 540bb46..fc59a22 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Androidスペルチェッカー"</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>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index bc2b628..b0e9d3f 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"입력 옵션"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 교정"</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>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/values-ky/donottranslate-more-keys.xml
similarity index 72%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/values-ky/donottranslate-more-keys.xml
index d5fd8ef..d56cde5 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/values-ky/donottranslate-more-keys.xml
@@ -17,11 +17,8 @@
 ** 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>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="more_keys_for_slavic_u">3,ү</string>
+    <string name="more_keys_for_slavic_en">6,ң</string>
+    <string name="more_keys_for_slavic_o">ө</string>
+</resources>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 4a5c5a4..dcbfe46 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -53,6 +53,7 @@
     <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>
diff --git a/java/res/values-lt/donottranslate-more-keys.xml b/java/res/values-lt/donottranslate-more-keys.xml
index 6b81e45..e36ce6a 100644
--- a/java/res/values-lt/donottranslate-more-keys.xml
+++ b/java/res/values-lt/donottranslate-more-keys.xml
@@ -18,11 +18,20 @@
 */
 -->
 <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_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_z">ž,ź,ż</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_k">ķ</string>
+    <string name="more_keys_for_l">ļ,ł,ĺ,ľ</string>
+    <string name="more_keys_for_g">ģ,ğ</string>
 </resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 6263f67..4c11bc2 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Įvesties parinktys"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"„Android“ korekcijos"</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>
diff --git a/java/res/values-lv/donottranslate-more-keys.xml b/java/res/values-lv/donottranslate-more-keys.xml
index 77e1c26..8514e73 100644
--- a/java/res/values-lv/donottranslate-more-keys.xml
+++ b/java/res/values-lv/donottranslate-more-keys.xml
@@ -18,16 +18,20 @@
 */
 -->
 <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_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_r">4,ŗ</string>
-    <string name="more_keys_for_z">ž,ź,ż</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_k">ķ</string>
-    <string name="more_keys_for_l">ļ,ł</string>
-    <string name="more_keys_for_g">ģ</string>
+    <string name="more_keys_for_l">ļ,ł,ĺ,ľ</string>
+    <string name="more_keys_for_g">ģ,ğ</string>
 </resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index af01d92..874ded1 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Ievades opcijas"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android korekcija"</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>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index d6bf559..caee144 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Pembetulan Android"</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>
diff --git a/java/res/values-nb/donottranslate-more-keys.xml b/java/res/values-nb/donottranslate-more-keys.xml
index b98341c..e83d6b7 100644
--- a/java/res/values-nb/donottranslate-more-keys.xml
+++ b/java/res/values-nb/donottranslate-more-keys.xml
@@ -22,6 +22,7 @@
     <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_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>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index acd636b..5f20aac 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inndataalternativer"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-stavekontroll"</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>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 7f0ff7e..97bcd90 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropties"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-spellingcontrole"</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>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index a9a6a76..cde1009 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcje wprowadzania"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Korekta Android"</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>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 0a30235..791d5f7 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 36a70da..50f538e 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 090f3fc..5e121f4 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -28,9 +28,9 @@
     <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>
diff --git a/java/res/values-ro/donottranslate-more-keys.xml b/java/res/values-ro/donottranslate-more-keys.xml
index d7e6a17..51df560 100644
--- a/java/res/values-ro/donottranslate-more-keys.xml
+++ b/java/res/values-ro/donottranslate-more-keys.xml
@@ -18,7 +18,7 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ă,â,à,á,ä,æ,ã,å,ā</string>
+    <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>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index c1dea54..ba27c18 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-ru/donottranslate-more-keys.xml b/java/res/values-ru/donottranslate-more-keys.xml
index f7e006e..7ae9ffb 100644
--- a/java/res/values-ru/donottranslate-more-keys.xml
+++ b/java/res/values-ru/donottranslate-more-keys.xml
@@ -18,7 +18,5 @@
 */
 -->
 <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>
+    <string name="more_keys_for_slavic_ye">5,ё</string>
 </resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 863c8a2..c23b2c3 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Параметры ввода"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Исправления Android"</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>
@@ -145,8 +145,8 @@
     <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..b6b35c1 100644
--- a/java/res/values-sk/donottranslate-more-keys.xml
+++ b/java/res/values-sk/donottranslate-more-keys.xml
@@ -18,18 +18,20 @@
 */
 -->
 <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_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>
+    <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_k">ķ</string>
+    <string name="more_keys_for_l">ľ,ĺ,ļ,ł</string>
+    <string name="more_keys_for_g">ģ,ğ</string>
 </resources>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index b7ab8f2..44d0af8 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/values-sl/donottranslate-more-keys.xml
similarity index 70%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/values-sl/donottranslate-more-keys.xml
index d5fd8ef..b72c679 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/values-sl/donottranslate-more-keys.xml
@@ -17,11 +17,9 @@
 ** 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>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="more_keys_for_s">š</string>
+    <string name="more_keys_for_c">č,ć</string>
+    <string name="more_keys_for_d">đ</string>
+    <string name="more_keys_for_z">ž</string>
+</resources>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index d7f357a..ba3d0c6 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 0906fce..1d199b1 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опције уноса"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android исправљање"</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>
diff --git a/java/res/values-sv/donottranslate-more-keys.xml b/java/res/values-sv/donottranslate-more-keys.xml
index 1fa29a8..d391be5 100644
--- a/java/res/values-sv/donottranslate-more-keys.xml
+++ b/java/res/values-sv/donottranslate-more-keys.xml
@@ -22,6 +22,7 @@
     <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_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>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 46760bb..a75736e 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inmatningsalternativ"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-korrigering"</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>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 822907b..88ea85d 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index 1b8c8a6..1c725a4 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -48,6 +48,7 @@
     <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>
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 1854a86..1dd9312 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -38,6 +38,5 @@
     <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>
     <integer name="config_max_more_keys_column">5</integer>
 </resources>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 3183023..e393be5 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -59,6 +59,7 @@
     <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>
 
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..06553a7 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -36,7 +36,6 @@
     <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>
     <integer name="config_max_more_keys_column">5</integer>
     <!--  Screen metrics for logging.
             0 = "mdpi phone screen"
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index bb4937d..dbbd844 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -62,6 +62,7 @@
     <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>
 
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 53f5660..1a3afaf 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"การแก้ไขของ Android"</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>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 701963f..3b4e4d9 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 4ae7d78..7ee12eb 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/values-uk/donottranslate-more-keys.xml
similarity index 75%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/values-uk/donottranslate-more-keys.xml
index d5fd8ef..4e79101 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/values-uk/donottranslate-more-keys.xml
@@ -17,11 +17,7 @@
 ** 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>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="keylabel_for_slavic_yery">і</string>
+    <string name="more_keys_for_slavic_yery">ї</string>
+</resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 234a9c5..ff1608f 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Парам. введення"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Виправлення Android"</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>
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..97a7d79
--- /dev/null
+++ b/java/res/values-vi/donottranslate-more-keys.xml
@@ -0,0 +1,28 @@
+<?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_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">6,ỳ,ý,ỷ,ỹ,ỵ</string>
+    <string name="more_keys_for_d">đ</string>
+</resources>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index e602b49..d9c524e 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index c3adf23..5162ea7 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 更正"</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>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 8d60fb7..fdb11a0 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -25,8 +25,8 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 拼字修正服務"</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>
@@ -50,7 +50,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"一律顯示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"以垂直模式顯示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"永遠隱藏"</string>
-    <string name="prefs_settings_key" msgid="4623341240804046498">"顯示設定金鑰"</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>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 072d17c..05da50c 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -25,8 +25,8 @@
     <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="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>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 0c9ca4f..57930c6 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -18,10 +18,13 @@
     <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" />
+        <!-- LatinKeyboardView style -->
+        <attr name="latinKeyboardViewStyle" format="reference" />
         <!-- MiniKeyboard style -->
         <attr name="miniKeyboardStyle" format="reference" />
         <!-- MiniKeyboardView style -->
@@ -120,6 +123,15 @@
         </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" />
+    </declare-styleable>
+
     <declare-styleable name="SuggestionsView">
         <attr name="suggestionStripOption" format="integer">
             <!-- This should be aligned with SuggestionsViewParams.AUTO_CORRECT_* and etc. -->
@@ -127,9 +139,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,8 +178,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. -->
@@ -178,13 +190,17 @@
         <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" />
     </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" />
         <!-- Maximum column of more keys keyboard -->
@@ -196,17 +212,22 @@
             <enum name="action" value="2" />
             <enum name="sticky" value="3" />
         </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" />
+        </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" />
@@ -224,7 +245,7 @@
         </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 KeyboardIcons.ICONS_TO_ATTRS_MAP -->
             <enum name="iconShiftKey" value="1" />
             <enum name="iconDeleteKey" value="2" />
             <enum name="iconSettingsKey" value="3" />
@@ -234,21 +255,21 @@
             <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" />
         </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 KeyboardIcons.ICONS_TO_ATTRS_MAP -->
+            <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 KeyboardIcons.ICONS_TO_ATTRS_MAP -->
+            <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" />
@@ -316,11 +337,28 @@
         <attr name="parentStyle" format="string" />
     </declare-styleable>
 
-    <declare-styleable name="LatinKeyboard">
-        <attr name="autoCorrectionSpacebarLedEnabled" format="boolean" />
-        <attr name="autoCorrectionSpacebarLedIcon" format="reference" />
-        <attr name="disabledShortcutIcon" format="reference" />
-        <attr name="spacebarTextColor" format="color" />
-        <attr name="spacebarTextShadowColor" format="color" />
+    <declare-styleable name="KeyboardSet">
+        <!-- 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" />
+          <!--  TODO: Implement alphabet variant shift keyboards
+          <enum name="alphabetManualTemporaryShift" value="1" />
+          <enum name="alphabetAutomaticTemporaryShift" value="2" />
+          <enum name="alphabetShiftLock" value="3" />
+          <enum name="alphabetShiftLockShift" value="4" />
+          -->
+          <enum name="symbols" value="5" />
+          <enum name="symbolsShift" value="6"  />
+          <enum name="phone" value="7"  />
+          <enum name="phoneShift" value="8"  />
+          <enum name="number" value="9"  />
+        </attr>
+        <attr name="elementKeyboard" format="reference" />
+        <!-- TODO: Add setShifted and setShiftLocked attribute. -->
     </declare-styleable>
 </resources>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 3f676ab..5e68043 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -25,7 +25,8 @@
     <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>
+    <!-- TODO: Disable the following configuration for production. -->
+    <bool name="config_enable_usability_study_mode_option">true</bool>
     <bool name="config_sliding_key_input_enabled">true</bool>
     <bool name="config_digit_more_keys_enabled">true</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
@@ -38,7 +39,6 @@
     <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 -->
@@ -67,7 +67,6 @@
     <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>
     <string-array name="auto_correction_threshold_values" translatable="false">
         <!-- Off, When auto correction setting is Off, this value is not used. -->
@@ -81,8 +80,8 @@
              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 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.
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 352141c..e46ff77 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -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>
 
diff --git a/java/res/values/donottranslate-more-keys.xml b/java/res/values/donottranslate-more-keys.xml
index 6c77539..ce15381 100644
--- a/java/res/values/donottranslate-more-keys.xml
+++ b/java/res/values/donottranslate-more-keys.xml
@@ -38,13 +38,21 @@
     <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="keylabel_for_slavic_shcha">щ</string>
+    <string name="keylabel_for_slavic_yery">ы</string>
+    <string name="keylabel_for_slavic_i">и</string>
+    <string name="more_keys_for_slavic_u">3</string>
+    <string name="more_keys_for_slavic_ye">5</string>
+    <string name="more_keys_for_slavic_en">6</string>
+    <string name="more_keys_for_slavic_ha">ъ</string>
+    <string name="more_keys_for_slavic_yery"></string>
+    <string name="more_keys_for_slavic_o"></string>
+    <string name="more_keys_for_slavic_soft_sign">ъ</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>
@@ -83,10 +91,8 @@
     <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_f1_settings">\@icon/settingsKey|\@integer/key_settings</string>
+    <string name="more_keys_for_f1_navigate">\@icon/tabKey|\@integer/key_tab</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>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index aefaec9..8bd25c0 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -120,6 +120,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 +162,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..1c5a5f7 100644
--- a/java/res/values/keyboard-icons-black.xml
+++ b/java/res/values/keyboard-icons-black.xml
@@ -30,10 +30,9 @@
         <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>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-ics.xml b/java/res/values/keyboard-icons-ics.xml
index f102143..f68be5f 100644
--- a/java/res/values/keyboard-icons-ics.xml
+++ b/java/res/values/keyboard-icons-ics.xml
@@ -23,16 +23,15 @@
         <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>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-white.xml b/java/res/values/keyboard-icons-white.xml
index 07ece66..35197a1 100644
--- a/java/res/values/keyboard-icons-white.xml
+++ b/java/res/values/keyboard-icons-white.xml
@@ -26,10 +26,10 @@
         <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>
     </style>
 </resources>
diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index 59cc075..b3f71d2 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -25,7 +25,10 @@
     <integer name="key_space">32</integer>
     <integer name="key_shift">-1</integer>
     <integer name="key_switch_alpha_symbol">-2</integer>
+    <integer name="key_capslock">-3</integer>
+    <integer name="key_output_text">-4</integer>
     <integer name="key_delete">-5</integer>
     <integer name="key_settings">-6</integer>
     <integer name="key_shortcut">-7</integer>
+    <integer name="key_unspecified">-9</integer>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index e00547a..f7d8b45 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -20,6 +20,8 @@
 <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] -->
@@ -31,11 +33,11 @@
     <!-- 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>
@@ -333,8 +335,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..c4e39e3 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -31,12 +31,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>
@@ -72,6 +66,15 @@
         <item name="backgroundDimAmount">0.5</item>
     </style>
     <style
+        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="MiniKeyboard"
         parent="Keyboard"
     >
@@ -98,7 +101,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>
@@ -133,6 +137,16 @@
         <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"
@@ -146,13 +160,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"
     >
@@ -166,6 +173,16 @@
         <item name="shadowColor">#FFFFFFFF</item>
     </style>
     <style
+        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="MiniKeyboard.Stone"
         parent="Keyboard.Stone"
     >
@@ -194,6 +211,16 @@
     >
         <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"
@@ -213,6 +240,16 @@
         <item name="keyTextStyle">bold</item>
     </style>
     <style
+        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="MiniKeyboard.Gingerbread"
         parent="Keyboard.Gingerbread"
     >
@@ -239,12 +276,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"
     >
@@ -268,6 +299,16 @@
         <item name="shadowRadius">0.0</item>
     </style>
     <style
+        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="MiniKeyboard.IceCreamSandwich"
         parent="Keyboard.IceCreamSandwich"
     >
@@ -296,11 +337,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>
diff --git a/java/res/values/themes-basic-highcontrast.xml b/java/res/values/themes-basic-highcontrast.xml
index abb7c80..0062b28 100644
--- a/java/res/values/themes-basic-highcontrast.xml
+++ b/java/res/values/themes-basic-highcontrast.xml
@@ -17,8 +17,8 @@
 <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="latinKeyboardViewStyle">@style/LatinKeyboardView.HighContrast</item>
         <item name="miniKeyboardStyle">@style/MiniKeyboard</item>
         <item name="miniKeyboardViewStyle">@style/MiniKeyboardView</item>
         <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
diff --git a/java/res/values/themes-basic.xml b/java/res/values/themes-basic.xml
index ff9fed5..0786e08 100644
--- a/java/res/values/themes-basic.xml
+++ b/java/res/values/themes-basic.xml
@@ -17,8 +17,8 @@
 <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="latinKeyboardViewStyle">@style/LatinKeyboardView</item>
         <item name="miniKeyboardStyle">@style/MiniKeyboard</item>
         <item name="miniKeyboardViewStyle">@style/MiniKeyboardView</item>
         <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
diff --git a/java/res/values/themes-gingerbread.xml b/java/res/values/themes-gingerbread.xml
index be853eb..44338d8 100644
--- a/java/res/values/themes-gingerbread.xml
+++ b/java/res/values/themes-gingerbread.xml
@@ -17,8 +17,8 @@
 <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="latinKeyboardViewStyle">@style/LatinKeyboardView.Gingerbread</item>
         <item name="miniKeyboardStyle">@style/MiniKeyboard.Gingerbread</item>
         <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.Gingerbread</item>
         <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 618aaed..dbc8f32 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -17,8 +17,8 @@
 <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="latinKeyboardViewStyle">@style/LatinKeyboardView.IceCreamSandwich</item>
         <item name="miniKeyboardStyle">@style/MiniKeyboard.IceCreamSandwich</item>
         <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.IceCreamSandwich</item>
         <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle.IceCreamSandwich</item>
diff --git a/java/res/values/themes-stone-bold.xml b/java/res/values/themes-stone-bold.xml
index 532a298..60f130d 100644
--- a/java/res/values/themes-stone-bold.xml
+++ b/java/res/values/themes-stone-bold.xml
@@ -17,8 +17,8 @@
 <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="latinKeyboardViewStyle">@style/LatinKeyboardView.Stone.Bold</item>
         <item name="miniKeyboardStyle">@style/MiniKeyboard.Stone</item>
         <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.Stone</item>
         <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
diff --git a/java/res/values/themes-stone.xml b/java/res/values/themes-stone.xml
index cb3edc5..9aaca3a 100644
--- a/java/res/values/themes-stone.xml
+++ b/java/res/values/themes-stone.xml
@@ -17,8 +17,8 @@
 <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="latinKeyboardViewStyle">@style/LatinKeyboardView.Stone</item>
         <item name="miniKeyboardStyle">@style/MiniKeyboard.Stone</item>
         <item name="miniKeyboardViewStyle">@style/MiniKeyboardView.Stone</item>
         <item name="miniKeyboardPanelStyle">@style/MiniKeyboardPanelStyle</item>
diff --git a/java/res/xml-ar/keyboard_set.xml b/java/res/xml-ar/keyboard_set.xml
new file mode 100644
index 0000000..68dc34f
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..e5c6ba3
--- /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_slavic" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..a789de6
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..94076d3
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-da/kbd_qwerty.xml b/java/res/xml-da/kbd_qwerty.xml
deleted file mode 100644
index 37a50fd..0000000
--- a/java/res/xml-da/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="da"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
diff --git a/java/res/xml-da/keyboard_set.xml b/java/res/xml-da/keyboard_set.xml
new file mode 100644
index 0000000..84d126d
--- /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_scandinavian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
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..2309729
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..c178836
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-es/keyboard_set.xml b/java/res/xml-es/keyboard_set.xml
new file mode 100644
index 0000000..77f3786
--- /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_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..304328a
--- /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_scandinavian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fi/keyboard_set.xml b/java/res/xml-fi/keyboard_set.xml
new file mode 100644
index 0000000..0c3a96d
--- /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_scandinavian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..b3bb4cc
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..d6dcf75
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-fr/keyboard_set.xml b/java/res/xml-fr/keyboard_set.xml
new file mode 100644
index 0000000..b94a3f2
--- /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_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..1d8582c
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-hu/keyboard_set.xml b/java/res/xml-hu/keyboard_set.xml
new file mode 100644
index 0000000..0771119
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-iw/keyboard_set.xml b/java/res/xml-iw/keyboard_set.xml
new file mode 100644
index 0000000..ce25aae
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..7cdd0a1
--- /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_slavic" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..eacda8f
--- /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_scandinavian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..89bd72f
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..de31e0b
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..725cb52
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..0a158d9
--- /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_slavic" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..9df01dd
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..d2ec4c0
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sr/keyboard_set.xml b/java/res/xml-sr/keyboard_set.xml
new file mode 100644
index 0000000..e72a0f2
--- /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_serbian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sv/kbd_qwerty.xml
deleted file mode 100644
index e29d9ab..0000000
--- a/java/res/xml-sv/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="sv"
->
-    <include
-        latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
-</Keyboard>
diff --git a/java/res/xml-sv/keyboard_set.xml b/java/res/xml-sv/keyboard_set.xml
new file mode 100644
index 0000000..df06aef
--- /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_scandinavian" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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-sw600dp-land/kbd_number.xml
similarity index 85%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_number.xml
index d5fd8ef..7e3188b 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_number.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_number" />
 </Keyboard>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml-sw600dp-land/kbd_phone.xml
similarity index 85%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_phone.xml
index d5fd8ef..28df7ef 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_phone.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml-sw600dp-land/kbd_phone_shift.xml
similarity index 84%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/xml-sw600dp-land/kbd_phone_shift.xml
index d5fd8ef..daf1d18 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml-sw600dp-land/kbd_phone_shift.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_key_styles.xml b/java/res/xml-sw600dp/kbd_key_styles.xml
index 25fa8b2..abba592 100644
--- a/java/res/xml-sw600dp/kbd_key_styles.xml
+++ b/java/res/xml-sw600dp/kbd_key_styles.xml
@@ -33,8 +33,8 @@
         <default>
             <key-style
                 latin:styleName="f2PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
-                latin:moreKeys="\@icon/3|\@integer/key_settings"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="\@icon/settingsKey|\@integer/key_settings"
                 latin:backgroundType="functional" />
         </default>
     </switch>
@@ -43,41 +43,47 @@
         latin:styleName="shiftKeyStyle"
         latin:code="@integer/key_shift"
         latin:keyIcon="iconShiftKey"
-        latin:keyIconShifted="iconShiftedShiftKey"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="sticky" />
     <key-style
         latin:styleName="deleteKeyStyle"
         latin:code="@integer/key_delete"
         latin:keyIcon="iconDeleteKey"
-        latin:backgroundType="functional"
-        latin:isRepeatable="true" />
+        latin:keyActionFlags="isRepeatable|noKeyPreview"
+        latin:backgroundType="functional" />
     <key-style
         latin:styleName="returnKeyStyle"
         latin:code="@integer/key_return"
         latin:keyIcon="iconReturnKey"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="spaceKeyStyle"
-        latin:code="@integer/key_space" />
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview" />
     <key-style
         latin:styleName="nonSpecialBackgroundSpaceKeyStyle"
-        latin:code="@integer/key_space" />
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview" />
     <key-style
         latin:styleName="smileyKeyStyle"
         latin:keyLabel=":-)"
         latin:keyOutputText=":-) "
-        latin:keyLabelOption="hasPopupHint"
+        latin:keyLabelFlags="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:keyIconDisabled="iconDisabledShortcutKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
         latin:parentStyle="f2PopupStyle" />
     <key-style
         latin:styleName="settingsKeyStyle"
         latin:code="@integer/key_settings"
         latin:keyIcon="iconSettingsKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="tabKeyStyle"
@@ -89,26 +95,30 @@
         latin:styleName="toSymbolKeyStyle"
         latin:code="@integer/key_switch_alpha_symbol"
         latin:keyLabel="@string/label_to_symbol_key"
+        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: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:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="backFromMoreSymbolKeyStyle"
         latin:code="@integer/key_shift"
         latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="comKeyStyle"
         latin:keyLabel="@string/keylabel_for_popular_domain"
-        latin:keyLabelOption="fontNormal|hasPopupHint"
+        latin:keyLabelFlags="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_number.xml b/java/res/xml-sw600dp/kbd_number.xml
index 46114de..ad588d7 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/kbd_rows_number" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_phone.xml b/java/res/xml-sw600dp/kbd_phone.xml
index 303f814..ce769b8 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/kbd_rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_phone_shift.xml b/java/res/xml-sw600dp/kbd_phone_shift.xml
index 4c4f8ad..3753deb 100644
--- a/java/res/xml-sw600dp/kbd_phone_shift.xml
+++ b/java/res/xml-sw600dp/kbd_phone_shift.xml
@@ -20,116 +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="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:code="44"
-            latin:keyLabel="@string/label_pause_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
-            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="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:code="59"
-            latin:keyLabel="@string/label_wait_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
-            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="backFromMoreSymbolKeyStyle"
-            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="N"
-            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:keyWidth="27.75%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/kbd_rows_phone_shift" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_qwerty_row4.xml b/java/res/xml-sw600dp/kbd_qwerty_row4.xml
index ef02922..54ca22b 100644
--- a/java/res/xml-sw600dp/kbd_qwerty_row4.xml
+++ b/java/res/xml-sw600dp/kbd_qwerty_row4.xml
@@ -45,7 +45,7 @@
             <default>
                 <Key
                     latin:keyLabel="/"
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\@"
                     latin:moreKeys="\@" />
             </default>
diff --git a/java/res/xml-sw600dp/kbd_row3_comma_period.xml b/java/res/xml-sw600dp/kbd_row3_comma_period.xml
index b844430..6a95ca1 100644
--- a/java/res/xml-sw600dp/kbd_row3_comma_period.xml
+++ b/java/res/xml-sw600dp/kbd_row3_comma_period.xml
@@ -33,12 +33,12 @@
         <default>
             <Key
                 latin:keyLabel=","
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="!"
                 latin:moreKeys="!" />
             <Key
                 latin:keyLabel="."
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="\?"
                 latin:moreKeys="\?" />
         </default>
diff --git a/java/res/xml-sw600dp/kbd_row3_smiley.xml b/java/res/xml-sw600dp/kbd_row3_smiley.xml
index f9b647c..c94ec0c 100644
--- a/java/res/xml-sw600dp/kbd_row3_smiley.xml
+++ b/java/res/xml-sw600dp/kbd_row3_smiley.xml
@@ -35,7 +35,7 @@
         >
             <Key
                 latin:keyLabel="-"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="_"
                 latin:moreKeys="_"
                 latin:keyXPos="-8.9%p"
@@ -46,7 +46,7 @@
         >
             <Key
                 latin:keyLabel=":"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="+"
                 latin:moreKeys="+"
                 latin:keyXPos="-8.9%p"
diff --git a/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml b/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
index 9536e81..4eb82d2 100644
--- a/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
+++ b/java/res/xml-sw600dp/kbd_row4_apostrophe_dash.xml
@@ -33,14 +33,14 @@
         >
             <Key
                 latin:keyLabel="/"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel=":"
                 latin:moreKeys=":" />
         </case>
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_apostrophe"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_apostrophe"
                 latin:moreKeys="@string/more_keys_for_apostrophe" />
         </default>
@@ -55,7 +55,7 @@
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_dash"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_dash"
                 latin:moreKeys="@string/more_keys_for_dash" />
         </default>
diff --git a/java/res/xml-sw600dp/kbd_rows_arabic.xml b/java/res/xml-sw600dp/kbd_rows_arabic.xml
index c2d3cd4..55c02f2 100644
--- a/java/res/xml-sw600dp/kbd_rows_arabic.xml
+++ b/java/res/xml-sw600dp/kbd_rows_arabic.xml
@@ -158,7 +158,7 @@
             >
                 <Key
                     latin:keyLabel="-"
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="_"
                     latin:moreKeys="_" />
             </case>
@@ -167,7 +167,7 @@
             >
                 <Key
                     latin:keyLabel=":"
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="+"
                     latin:moreKeys="+" />
             </case>
diff --git a/java/res/xml-sw600dp/kbd_rows_azerty.xml b/java/res/xml-sw600dp/kbd_rows_azerty.xml
index 8ae7455..4696789 100644
--- a/java/res/xml-sw600dp/kbd_rows_azerty.xml
+++ b/java/res/xml-sw600dp/kbd_rows_azerty.xml
@@ -132,12 +132,12 @@
             <default>
                 <Key
                     latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="!"
                     latin:moreKeys="!" />
                 <Key
                     latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\?"
                     latin:moreKeys="\?" />
             </default>
diff --git a/java/res/xml-sw600dp/kbd_rows_russian.xml b/java/res/xml-sw600dp/kbd_rows_bulgarian.xml
similarity index 80%
copy from java/res/xml-sw600dp/kbd_rows_russian.xml
copy to java/res/xml-sw600dp/kbd_rows_bulgarian.xml
index cc9ad3a..746398d 100644
--- a/java/res/xml-sw600dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw600dp/kbd_rows_bulgarian.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.
@@ -24,91 +24,86 @@
     <include
         latin:keyboardLayout="@xml/kbd_key_styles" />
     <Row
-        latin:keyWidth="7.60%p"
+        latin:keyWidth="7.692%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="с" />
+            latin:keyLabel="ш" />
         <Key
-            latin:keyLabel="м" />
+            latin:keyLabel="е" />
         <Key
-            latin:keyLabel="и" />
+            latin:keyLabel="р" />
         <Key
             latin:keyLabel="т" />
         <Key
+            latin:keyLabel="ъ" />
+        <Key
+            latin:keyLabel="у" />
+        <Key
+            latin:keyLabel="и"
+            latin:moreKeys="ѝ" />
+        <Key
+            latin:keyLabel="о" />
+        <Key
+            latin:keyLabel="п" />
+        <Key
+            latin:keyLabel="я" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.692%p"
+    >
+        <Key
+            latin:keyLabel="а"
+            latin:keyXPos="4.000%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:keyWidth="fillBoth" />
+    </Row>
+    <Row
+        latin:keyWidth="7.692%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.000%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="ю" />
         <switch>
             <case
@@ -122,16 +117,19 @@
             <default>
                 <Key
                     latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="!"
                     latin:moreKeys="!" />
                 <Key
                     latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\?"
                     latin:moreKeys="\?" />
             </default>
         </switch>
+        <Spacer
+            latin:keyXPos="-10.000%p"
+            latin:keyWidth="0%p" />
         <include
             latin:keyboardLayout="@xml/kbd_row3_smiley" />
     </Row>
diff --git a/java/res/xml-sw600dp/kbd_rows_hebrew.xml b/java/res/xml-sw600dp/kbd_rows_hebrew.xml
index a8adbd3..4166745 100644
--- a/java/res/xml-sw600dp/kbd_rows_hebrew.xml
+++ b/java/res/xml-sw600dp/kbd_rows_hebrew.xml
@@ -94,7 +94,7 @@
             >
                 <Key
                     latin:keyLabel="-"
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="_"
                     latin:moreKeys="_"
                     latin:keyWidth="10.0%p" />
@@ -104,7 +104,7 @@
             >
                 <Key
                     latin:keyLabel=":"
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="+"
                     latin:moreKeys="+"
                     latin:keyWidth="10.0%p" />
diff --git a/java/res/xml-sw600dp/kbd_rows_number.xml b/java/res/xml-sw600dp/kbd_rows_number.xml
new file mode 100644
index 0000000..cfb2421
--- /dev/null
+++ b/java/res/xml-sw600dp/kbd_rows_number.xml
@@ -0,0 +1,208 @@
+<?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>
+                <!-- 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="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="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/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="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" />
+                <Key
+                    latin:keyLabel=","
+                    latin:keyStyle="numKeyStyle"
+                    latin:keyWidth="9.25%p" />
+                <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="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="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="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="nonSpecialBackgroundSpaceKeyStyle"
+                    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/kbd_qwerty_f2" />
+            </Row>
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_phone.xml b/java/res/xml-sw600dp/kbd_rows_phone.xml
new file mode 100644
index 0000000..69d058f
--- /dev/null
+++ b/java/res/xml-sw600dp/kbd_rows_phone.xml
@@ -0,0 +1,122 @@
+<?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" />
+    <Row>
+        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
+        <Spacer
+            latin:keyWidth="17.375%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="42.25%p" />
+        <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="17.375%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="42.25%p" />
+        <Key
+            latin:keyStyle="num5KeyStyle" />
+        <Key
+            latin:keyStyle="num6KeyStyle" />
+        <Key
+            latin:keyStyle="returnKeyStyle"
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyWidth="11.0%p" />
+        <Key
+            latin:keyLabel="("
+            latin:keyStyle="numKeyStyle"
+            latin:keyXPos="17.375%p"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel=")"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyStyle="num7KeyStyle"
+            latin:keyXPos="42.25%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="numTabKeyStyle"
+            latin:keyWidth="11.00%p" />
+        <Key
+            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyWidth="18.50%p"
+            latin:keyXPos="17.375%p" />
+        <Key
+            latin:keyStyle="numStarKeyStyle"
+            latin:keyXPos="42.25%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>
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_phone_shift.xml b/java/res/xml-sw600dp/kbd_rows_phone_shift.xml
new file mode 100644
index 0000000..04db678
--- /dev/null
+++ b/java/res/xml-sw600dp/kbd_rows_phone_shift.xml
@@ -0,0 +1,132 @@
+<?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" />
+    <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:keyStyle="numPauseKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyStyle="num1KeyStyle"
+            latin:keyXPos="42.25%p" />
+        <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="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:keyStyle="numWaitKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyStyle="num4KeyStyle"
+            latin:keyXPos="42.25%p" />
+        <Key
+            latin:keyStyle="num5KeyStyle" />
+        <Key
+            latin:keyStyle="num6KeyStyle" />
+        <Key
+            latin:keyStyle="returnKeyStyle"
+            latin:keyXPos="-11.00%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyStyle="backFromMoreSymbolKeyStyle"
+            latin:keyWidth="11.00%p" />
+        <Key
+            latin:keyLabel="("
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p"
+            latin:keyXPos="12.75%p" />
+        <Key
+            latin:keyLabel=")"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyLabel="N"
+            latin:keyStyle="numKeyStyle"
+            latin:keyWidth="9.25%p" />
+        <Key
+            latin:keyStyle="num7KeyStyle"
+            latin:keyXPos="42.25%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="numTabKeyStyle"
+            latin:keyWidth="11.00%p" />
+        <Key
+            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyWidth="27.75%p"
+            latin:keyXPos="12.75%p" />
+        <Key
+            latin:keyStyle="numStarKeyStyle"
+            latin:keyXPos="42.25%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>
+</merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_qwertz.xml b/java/res/xml-sw600dp/kbd_rows_qwertz.xml
index 98667e0..d7d13d5 100644
--- a/java/res/xml-sw600dp/kbd_rows_qwertz.xml
+++ b/java/res/xml-sw600dp/kbd_rows_qwertz.xml
@@ -99,12 +99,12 @@
             <default>
                 <Key
                     latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="!"
                     latin:moreKeys="!" />
                 <Key
                     latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\?"
                     latin:moreKeys="\?" />
             </default>
diff --git a/java/res/xml-sw600dp/kbd_rows_scandinavian.xml b/java/res/xml-sw600dp/kbd_rows_scandinavian.xml
index 19fb521..6d14d89 100644
--- a/java/res/xml-sw600dp/kbd_rows_scandinavian.xml
+++ b/java/res/xml-sw600dp/kbd_rows_scandinavian.xml
@@ -57,7 +57,7 @@
             latin:keyLabel="p"
             latin:moreKeys="@string/more_keys_for_p" />
         <Key
-            latin:keyLabel="å" />
+            latin:keyLabel="@string/keylabel_for_scandinavia_row1_11" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyXPos="-10.0%p"
diff --git a/java/res/xml-sw600dp/kbd_rows_russian.xml b/java/res/xml-sw600dp/kbd_rows_slavic.xml
similarity index 82%
rename from java/res/xml-sw600dp/kbd_rows_russian.xml
rename to java/res/xml-sw600dp/kbd_rows_slavic.xml
index cc9ad3a..0a16205 100644
--- a/java/res/xml-sw600dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw600dp/kbd_rows_slavic.xml
@@ -31,20 +31,22 @@
         <Key
             latin:keyLabel="ц" />
         <Key
-            latin:keyLabel="у" />
+            latin:keyLabel="у"
+            latin:moreKeys="@string/more_keys_for_slavic_u" />
         <Key
             latin:keyLabel="к" />
         <Key
             latin:keyLabel="е"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
+            latin:moreKeys="@string/more_keys_for_slavic_ye" />
         <Key
-            latin:keyLabel="н" />
+            latin:keyLabel="н"
+            latin:moreKeys="@string/more_keys_for_slavic_en" />
         <Key
             latin:keyLabel="г" />
         <Key
             latin:keyLabel="ш" />
         <Key
-            latin:keyLabel="щ" />
+            latin:keyLabel="@string/keylabel_for_slavic_shcha" />
         <Key
             latin:keyLabel="з" />
         <Key
@@ -63,7 +65,8 @@
             latin:keyLabel="ф"
             latin:keyXPos="2.25%p" />
         <Key
-            latin:keyLabel="ы" />
+            latin:keyLabel="@string/keylabel_for_slavic_yery"
+            latin:moreKeys="@string/more_keys_for_slavic_yery" />
         <Key
             latin:keyLabel="в" />
         <Key
@@ -73,7 +76,8 @@
         <Key
             latin:keyLabel="р" />
         <Key
-            latin:keyLabel="о" />
+            latin:keyLabel="о"
+            latin:moreKeys="@string/more_keys_for_slavic_o" />
         <Key
             latin:keyLabel="л" />
         <Key
@@ -101,7 +105,7 @@
         <Key
             latin:keyLabel="м" />
         <Key
-            latin:keyLabel="и" />
+            latin:keyLabel="@string/keylabel_for_slavic_i" />
         <Key
             latin:keyLabel="т" />
         <Key
@@ -122,12 +126,12 @@
             <default>
                 <Key
                     latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="!"
                     latin:moreKeys="!" />
                 <Key
                     latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\?"
                     latin:moreKeys="\?" />
             </default>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_number.xml
similarity index 84%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_number.xml
index d5fd8ef..3106dc3 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_number.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_number" />
 </Keyboard>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_phone.xml
similarity index 85%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_phone.xml
index d5fd8ef..7c7af57 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_phone.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml-sw768dp-land/kbd_phone_shift.xml
similarity index 84%
copy from java/res/xml-de-rZZ/kbd_qwerty.xml
copy to java/res/xml-sw768dp-land/kbd_phone_shift.xml
index d5fd8ef..04b018c 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml-sw768dp-land/kbd_phone_shift.xml
@@ -20,8 +20,9 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_phone_shift" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_key_styles.xml b/java/res/xml-sw768dp/kbd_key_styles.xml
index f16f5b6..72a1a4c 100644
--- a/java/res/xml-sw768dp/kbd_key_styles.xml
+++ b/java/res/xml-sw768dp/kbd_key_styles.xml
@@ -25,76 +25,86 @@
         latin:styleName="shiftKeyStyle"
         latin:code="@integer/key_shift"
         latin:keyIcon="iconShiftKey"
-        latin:keyIconShifted="iconShiftedShiftKey"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="sticky" />
     <key-style
         latin:styleName="deleteKeyStyle"
         latin:code="@integer/key_delete"
         latin:keyIcon="iconDeleteKey"
-        latin:backgroundType="functional"
-        latin:isRepeatable="true" />
+        latin:keyActionFlags="isRepeatable|noKeyPreview"
+        latin:backgroundType="functional" />
     <key-style
         latin:styleName="returnKeyStyle"
         latin:code="@integer/key_return"
         latin:keyIcon="iconReturnKey"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="spaceKeyStyle"
-        latin:code="@integer/key_space" />
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview" />
     <key-style
         latin:styleName="nonSpecialBackgroundSpaceKeyStyle"
-        latin:code="@integer/key_space" />
+        latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview" />
     <key-style
         latin:styleName="smileyKeyStyle"
         latin:keyLabel=":-)"
         latin:keyOutputText=":-) "
-        latin:keyLabelOption="hasPopupHint"
+        latin:keyLabelFlags="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:keyIconDisabled="iconDisabledShortcutKey"
+        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" />
     <key-style
         latin:styleName="tabKeyStyle"
         latin:code="@integer/key_tab"
         latin:keyLabel="@string/label_tab_key"
-        latin:keyLabelOption="fontNormal"
+        latin:keyLabelFlags="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:keyLabelFlags="fontNormal"
+        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:keyLabelOption="fontNormal"
+        latin:keyLabelFlags="fontNormal"
+        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:keyLabelOption="fontNormal"
+        latin:keyLabelFlags="fontNormal"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="backFromMoreSymbolKeyStyle"
         latin:code="@integer/key_shift"
         latin:keyLabel="@string/label_to_symbol_key"
-        latin:keyLabelOption="fontNormal"
+        latin:keyLabelFlags="fontNormal"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="comKeyStyle"
         latin:keyLabel="@string/keylabel_for_popular_domain"
-        latin:keyLabelOption="fontNormal|hasPopupHint"
+        latin:keyLabelFlags="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_number.xml b/java/res/xml-sw768dp/kbd_number.xml
index 369e91a..74ce854 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/kbd_rows_number" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_phone.xml b/java/res/xml-sw768dp/kbd_phone.xml
index e55b184..0a9b8b5 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/kbd_rows_phone" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_phone_shift.xml b/java/res/xml-sw768dp/kbd_phone_shift.xml
index 46f67d3..055d70c 100644
--- a/java/res/xml-sw768dp/kbd_phone_shift.xml
+++ b/java/res/xml-sw768dp/kbd_phone_shift.xml
@@ -23,136 +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="13.829%p"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyLabel="+"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:code="44"
-            latin:keyLabel="@string/label_pause_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
-            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="backFromMoreSymbolKeyStyle"
-            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:code="59"
-            latin:keyLabel="@string/label_wait_key"
-            latin:keyLabelOption="followKeyHintLabelRatio|autoXScale"
-            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="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="N"
-            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="13.829%p"
-            latin:keyWidth="24.140%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/kbd_rows_phone_shift" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row1.xml b/java/res/xml-sw768dp/kbd_qwerty_row1.xml
index 14b8bdd..de91013 100644
--- a/java/res/xml-sw768dp/kbd_qwerty_row1.xml
+++ b/java/res/xml-sw768dp/kbd_qwerty_row1.xml
@@ -26,7 +26,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <Key
             latin:keyLabel="q"
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row2.xml b/java/res/xml-sw768dp/kbd_qwerty_row2.xml
index 2c312a3..1129ecd 100644
--- a/java/res/xml-sw768dp/kbd_qwerty_row2.xml
+++ b/java/res/xml-sw768dp/kbd_qwerty_row2.xml
@@ -26,7 +26,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p"/>
         <Key
             latin:keyLabel="a"
diff --git a/java/res/xml-sw768dp/kbd_qwerty_row4.xml b/java/res/xml-sw768dp/kbd_qwerty_row4.xml
index e35e47d..1f00dff 100644
--- a/java/res/xml-sw768dp/kbd_qwerty_row4.xml
+++ b/java/res/xml-sw768dp/kbd_qwerty_row4.xml
@@ -57,7 +57,7 @@
                     >
                         <Key
                             latin:keyLabel=":"
-                            latin:keyLabelOption="hasUppercaseLetter"
+                            latin:keyLabelFlags="hasUppercaseLetter"
                             latin:keyHintLabel="+"
                             latin:moreKeys="+" />
                     </case>
@@ -76,7 +76,7 @@
                     <default>
                         <Key
                             latin:keyLabel="/"
-                            latin:keyLabelOption="hasUppercaseLetter"
+                            latin:keyLabelFlags="hasUppercaseLetter"
                             latin:keyHintLabel="\@"
                             latin:moreKeys="\@" />
                     </default>
diff --git a/java/res/xml-sw768dp/kbd_row3_comma_period.xml b/java/res/xml-sw768dp/kbd_row3_comma_period.xml
index b844430..6a95ca1 100644
--- a/java/res/xml-sw768dp/kbd_row3_comma_period.xml
+++ b/java/res/xml-sw768dp/kbd_row3_comma_period.xml
@@ -33,12 +33,12 @@
         <default>
             <Key
                 latin:keyLabel=","
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="!"
                 latin:moreKeys="!" />
             <Key
                 latin:keyLabel="."
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="\?"
                 latin:moreKeys="\?" />
         </default>
diff --git a/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml b/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml
index 9536e81..4eb82d2 100644
--- a/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml
+++ b/java/res/xml-sw768dp/kbd_row4_apostrophe_dash.xml
@@ -33,14 +33,14 @@
         >
             <Key
                 latin:keyLabel="/"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel=":"
                 latin:moreKeys=":" />
         </case>
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_apostrophe"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_apostrophe"
                 latin:moreKeys="@string/more_keys_for_apostrophe" />
         </default>
@@ -55,7 +55,7 @@
         <default>
             <Key
                 latin:keyLabel="@string/keylabel_for_dash"
-                latin:keyLabelOption="hasUppercaseLetter"
+                latin:keyLabelFlags="hasUppercaseLetter"
                 latin:keyHintLabel="@string/keyhintlabel_for_dash"
                 latin:moreKeys="@string/more_keys_for_dash" />
         </default>
diff --git a/java/res/xml-sw768dp/kbd_rows_arabic.xml b/java/res/xml-sw768dp/kbd_rows_arabic.xml
index 7ec36fd..412d5d9 100644
--- a/java/res/xml-sw768dp/kbd_rows_arabic.xml
+++ b/java/res/xml-sw768dp/kbd_rows_arabic.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.500%p" />
         <!-- \u0636: ARABIC LETTER DAD -->
         <Key
@@ -84,7 +84,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="9.375%p" />
         <!-- \u0634: ARABIC LETTER SHEEN
              \u069c: ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
diff --git a/java/res/xml-sw768dp/kbd_rows_azerty.xml b/java/res/xml-sw768dp/kbd_rows_azerty.xml
index 4659d99..3edfb7e 100644
--- a/java/res/xml-sw768dp/kbd_rows_azerty.xml
+++ b/java/res/xml-sw768dp/kbd_rows_azerty.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <Key
             latin:keyLabel="a"
@@ -70,7 +70,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="10.167%p" />
         <Key
             latin:keyLabel="q"
@@ -127,7 +127,7 @@
             latin:moreKeys="@string/more_keys_for_n" />
         <Key
             latin:keyLabel="\'"
-            latin:keyLabelOption="hasUppercaseLetter"
+            latin:keyLabelFlags="hasUppercaseLetter"
             latin:keyHintLabel=":"
             latin:moreKeys=":" />
         <switch>
@@ -142,12 +142,12 @@
             <default>
                 <Key
                     latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="!"
                     latin:moreKeys="!" />
                 <Key
                     latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\?"
                     latin:moreKeys="\?" />
             </default>
diff --git a/java/res/xml-sw768dp/kbd_rows_russian.xml b/java/res/xml-sw768dp/kbd_rows_bulgarian.xml
similarity index 84%
rename from java/res/xml-sw768dp/kbd_rows_russian.xml
rename to java/res/xml-sw768dp/kbd_rows_bulgarian.xml
index e5f5569..42b3da5 100644
--- a/java/res/xml-sw768dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw768dp/kbd_rows_bulgarian.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.
@@ -24,72 +24,69 @@
     <include
         latin:keyboardLayout="@xml/kbd_key_styles" />
     <Row
-        latin:keyWidth="7.125%p"
+        latin:keyWidth="7.333%p"
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft" />
+            latin:keyLabelFlags="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="г" />
+            latin:keyLabel="ч" />
         <Key
             latin:keyLabel="ш" />
         <Key
-            latin:keyLabel="щ" />
+            latin:keyLabel="е" />
         <Key
-            latin:keyLabel="з" />
+            latin:keyLabel="р" />
         <Key
-            latin:keyLabel="х" />
+            latin:keyLabel="т" />
         <Key
             latin:keyLabel="ъ" />
         <Key
+            latin:keyLabel="у" />
+        <Key
+            latin:keyLabel="и"
+            latin:moreKeys="ѝ" />
+        <Key
+            latin:keyLabel="о" />
+        <Key
+            latin:keyLabel="п" />
+        <Key
+            latin:keyLabel="я" />
+        <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillBoth" />
     </Row>
     <Row
-        latin:keyWidth="7.125%p"
+        latin:keyWidth="7.194%p"
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="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="л" />
+            latin:keyLabel="с" />
         <Key
             latin:keyLabel="д" />
         <Key
-            latin:keyLabel="ж" />
+            latin:keyLabel="ф" />
         <Key
-            latin:keyLabel="э" />
+            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
@@ -97,24 +94,22 @@
     >
         <Key
             latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="12.750%p" />
+            latin:keyWidth="14.375%p" />
         <Key
-            latin:keyLabel="я" />
+            latin:keyLabel="з" />
         <Key
-            latin:keyLabel="ч" />
+            latin:keyLabel="ж" />
         <Key
-            latin:keyLabel="с" />
+            latin:keyLabel="ц" />
         <Key
-            latin:keyLabel="м" />
-        <Key
-            latin:keyLabel="и" />
-        <Key
-            latin:keyLabel="т" />
-        <Key
-            latin:keyLabel="ь" />
+            latin:keyLabel="в" />
         <Key
             latin:keyLabel="б" />
         <Key
+            latin:keyLabel="н" />
+        <Key
+            latin:keyLabel="м" />
+        <Key
             latin:keyLabel="ю" />
         <include
             latin:keyboardLayout="@xml/kbd_row3_comma_period" />
diff --git a/java/res/xml-sw768dp/kbd_rows_hebrew.xml b/java/res/xml-sw768dp/kbd_rows_hebrew.xml
index 27b39d1..5f4b556 100644
--- a/java/res/xml-sw768dp/kbd_rows_hebrew.xml
+++ b/java/res/xml-sw768dp/kbd_rows_hebrew.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
             latin:keyboardLayout="@xml/kbd_row4_apostrophe_dash" />
@@ -58,7 +58,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
             latin:keyLabel="ש" />
diff --git a/java/res/xml-sw768dp/kbd_rows_number.xml b/java/res/xml-sw768dp/kbd_rows_number.xml
new file mode 100644
index 0000000..1268987
--- /dev/null
+++ b/java/res/xml-sw768dp/kbd_rows_number.xml
@@ -0,0 +1,227 @@
+<?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="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="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:keyLabelFlags="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>
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_phone.xml b/java/res/xml-sw768dp/kbd_rows_phone.xml
new file mode 100644
index 0000000..1320cf0
--- /dev/null
+++ b/java/res/xml-sw768dp/kbd_rows_phone.xml
@@ -0,0 +1,143 @@
+<?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" />
+    <Row>
+        <Key
+            latin:keyStyle="numTabKeyStyle"
+            latin:keyLabelFlags="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>
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_phone_shift.xml b/java/res/xml-sw768dp/kbd_rows_phone_shift.xml
new file mode 100644
index 0000000..e749790
--- /dev/null
+++ b/java/res/xml-sw768dp/kbd_rows_phone_shift.xml
@@ -0,0 +1,153 @@
+<?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" />
+    <Row>
+        <Key
+            latin:keyStyle="numTabKeyStyle"
+            latin:keyLabelFlags="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:keyStyle="numPauseKeyStyle"
+            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="backFromMoreSymbolKeyStyle"
+            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:keyStyle="numWaitKeyStyle"
+            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="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="N"
+            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="13.829%p"
+            latin:keyWidth="24.140%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>
+</merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_qwertz.xml b/java/res/xml-sw768dp/kbd_rows_qwertz.xml
index 82e0dd0..3c02c8f 100644
--- a/java/res/xml-sw768dp/kbd_rows_qwertz.xml
+++ b/java/res/xml-sw768dp/kbd_rows_qwertz.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <Key
             latin:keyLabel="q"
@@ -103,12 +103,12 @@
             <default>
                 <Key
                     latin:keyLabel=","
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="!"
                     latin:moreKeys="!" />
                 <Key
                     latin:keyLabel="."
-                    latin:keyLabelOption="hasUppercaseLetter"
+                    latin:keyLabelFlags="hasUppercaseLetter"
                     latin:keyHintLabel="\?"
                     latin:moreKeys="\?" />
             </default>
diff --git a/java/res/xml-sw768dp/kbd_rows_scandinavian.xml b/java/res/xml-sw768dp/kbd_rows_scandinavian.xml
index b9d1680..334b76d 100644
--- a/java/res/xml-sw768dp/kbd_rows_scandinavian.xml
+++ b/java/res/xml-sw768dp/kbd_rows_scandinavian.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.500%p" />
         <Key
             latin:keyLabel="q"
@@ -61,7 +61,7 @@
             latin:keyLabel="p"
             latin:moreKeys="@string/more_keys_for_p" />
         <Key
-            latin:keyLabel="å" />
+            latin:keyLabel="@string/keylabel_for_scandinavia_row1_11" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyXPos="-11.500%p"
@@ -72,7 +72,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="9.375%p" />
         <Key
             latin:keyLabel="a"
diff --git a/java/res/xml-sw768dp/kbd_rows_serbian.xml b/java/res/xml-sw768dp/kbd_rows_serbian.xml
index c07176e..0b1773e 100644
--- a/java/res/xml-sw768dp/kbd_rows_serbian.xml
+++ b/java/res/xml-sw768dp/kbd_rows_serbian.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft" />
+            latin:keyLabelFlags="alignLeft" />
         <Key
             latin:keyLabel="љ" />
         <Key
@@ -62,7 +62,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
             latin:keyLabel="а" />
diff --git a/java/res/xml-sw768dp/kbd_rows_russian.xml b/java/res/xml-sw768dp/kbd_rows_slavic.xml
similarity index 81%
copy from java/res/xml-sw768dp/kbd_rows_russian.xml
copy to java/res/xml-sw768dp/kbd_rows_slavic.xml
index e5f5569..4c9128d 100644
--- a/java/res/xml-sw768dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw768dp/kbd_rows_slavic.xml
@@ -28,26 +28,28 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft" />
+            latin:keyLabelFlags="alignLeft" />
         <Key
             latin:keyLabel="й" />
         <Key
             latin:keyLabel="ц" />
         <Key
-            latin:keyLabel="у" />
+            latin:keyLabel="у"
+            latin:moreKeys="@string/more_keys_for_slavic_u" />
         <Key
             latin:keyLabel="к" />
         <Key
             latin:keyLabel="е"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
+            latin:moreKeys="@string/more_keys_for_slavic_ye" />
         <Key
-            latin:keyLabel="н" />
+            latin:keyLabel="н"
+            latin:moreKeys="@string/more_keys_for_slavic_en" />
         <Key
             latin:keyLabel="г" />
         <Key
             latin:keyLabel="ш" />
         <Key
-            latin:keyLabel="щ" />
+            latin:keyLabel="@string/keylabel_for_slavic_shcha" />
         <Key
             latin:keyLabel="з" />
         <Key
@@ -63,12 +65,13 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="9.375%p" />
         <Key
             latin:keyLabel="ф" />
         <Key
-            latin:keyLabel="ы" />
+            latin:keyLabel="@string/keylabel_for_slavic_yery"
+            latin:moreKeys="@string/more_keys_for_slavic_yery" />
         <Key
             latin:keyLabel="в" />
         <Key
@@ -78,7 +81,8 @@
         <Key
             latin:keyLabel="р" />
         <Key
-            latin:keyLabel="о" />
+            latin:keyLabel="о"
+            latin:moreKeys="@string/more_keys_for_slavic_o" />
         <Key
             latin:keyLabel="л" />
         <Key
@@ -107,7 +111,7 @@
         <Key
             latin:keyLabel="м" />
         <Key
-            latin:keyLabel="и" />
+            latin:keyLabel="@string/keylabel_for_slavic_i" />
         <Key
             latin:keyLabel="т" />
         <Key
diff --git a/java/res/xml-sw768dp/kbd_rows_spanish.xml b/java/res/xml-sw768dp/kbd_rows_spanish.xml
index c737f40..7e543b2 100644
--- a/java/res/xml-sw768dp/kbd_rows_spanish.xml
+++ b/java/res/xml-sw768dp/kbd_rows_spanish.xml
@@ -30,7 +30,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="10.167%p" />
         <Key
             latin:keyLabel="a"
diff --git a/java/res/xml-sw768dp/kbd_rows_symbols.xml b/java/res/xml-sw768dp/kbd_rows_symbols.xml
index 987b10c..641fe19 100644
--- a/java/res/xml-sw768dp/kbd_rows_symbols.xml
+++ b/java/res/xml-sw768dp/kbd_rows_symbols.xml
@@ -30,7 +30,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <Key
             latin:keyLabel="@string/keylabel_for_symbols_1"
@@ -72,7 +72,7 @@
     >
         <Key
             latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
             latin:keyLabel="#" />
diff --git a/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml b/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
index 9a9c3a2..f6b47a8 100644
--- a/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
+++ b/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
@@ -30,7 +30,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <Key
             latin:keyLabel="~" />
@@ -65,7 +65,7 @@
     >
         <Key
             latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
             latin:keyStyle="moreCurrency1KeyStyle" />
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..854ab85
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..e5ba43b
--- /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_slavic" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <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..7f4b25d
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-ar/kbd_qwerty.xml b/java/res/xml/kbd_arabic.xml
similarity index 96%
rename from java/res/xml-ar/kbd_qwerty.xml
rename to java/res/xml/kbd_arabic.xml
index b26a938..91b2281 100644
--- a/java/res/xml-ar/kbd_qwerty.xml
+++ b/java/res/xml/kbd_arabic.xml
@@ -20,7 +20,6 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="ar"
     latin:isRtlKeyboard="true"
 >
     <include
diff --git a/java/res/xml-fr/kbd_qwerty.xml b/java/res/xml/kbd_azerty.xml
similarity index 90%
rename from java/res/xml-fr/kbd_qwerty.xml
rename to java/res/xml/kbd_azerty.xml
index 8c730a2..3abaf64 100644
--- a/java/res/xml-fr/kbd_qwerty.xml
+++ b/java/res/xml/kbd_azerty.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, 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.
@@ -20,7 +20,6 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="fr"
 >
     <include
         latin:keyboardLayout="@xml/kbd_rows_azerty" />
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml/kbd_bulgarian.xml
similarity index 83%
rename from java/res/xml-fr-rCA/kbd_qwerty.xml
rename to java/res/xml/kbd_bulgarian.xml
index 7bdfbad..f114383 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/kbd_rows_bulgarian" />
 </Keyboard>
diff --git a/java/res/xml-iw/kbd_qwerty.xml b/java/res/xml/kbd_hebrew.xml
similarity index 90%
rename from java/res/xml-iw/kbd_qwerty.xml
rename to java/res/xml/kbd_hebrew.xml
index 54cd4b5..f25aadd 100644
--- a/java/res/xml-iw/kbd_qwerty.xml
+++ b/java/res/xml/kbd_hebrew.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.
@@ -20,7 +20,6 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="iw"
     latin:isRtlKeyboard="true"
 >
     <include
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 453b05d..7869d21 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -28,7 +28,7 @@
         >
             <key-style
                 latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
+                latin:keyLabelFlags="hasPopupHint"
                 latin:moreKeys="@string/more_keys_for_f1"
                 latin:backgroundType="functional" />
         </case>
@@ -38,7 +38,7 @@
         >
             <key-style
                 latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
+                latin:keyLabelFlags="hasPopupHint"
                 latin:moreKeys="@string/more_keys_for_f1_settings"
                 latin:backgroundType="functional" />
         </case>
@@ -48,7 +48,7 @@
         >
             <key-style
                 latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
+                latin:keyLabelFlags="hasPopupHint"
                 latin:moreKeys="@string/more_keys_for_f1_navigate"
                 latin:backgroundType="functional" />
         </case>
@@ -56,7 +56,7 @@
         <default>
             <key-style
                 latin:styleName="f1PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
+                latin:keyLabelFlags="hasPopupHint"
                 latin:moreKeys="@string/more_keys_for_f1"
                 latin:backgroundType="functional" />
         </default>
@@ -66,14 +66,14 @@
         latin:styleName="shiftKeyStyle"
         latin:code="@integer/key_shift"
         latin:keyIcon="iconShiftKey"
-        latin:keyIconShifted="iconShiftedShiftKey"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="sticky" />
     <key-style
         latin:styleName="deleteKeyStyle"
         latin:code="@integer/key_delete"
         latin:keyIcon="iconDeleteKey"
-        latin:backgroundType="functional"
-        latin:isRepeatable="true" />
+        latin:keyActionFlags="isRepeatable|noKeyPreview"
+        latin:backgroundType="functional" />
     <!-- Return key style -->
     <switch>
         <case
@@ -84,7 +84,7 @@
                 latin:styleName="returnKeyStyle"
                 latin:keyLabel=":-)"
                 latin:keyOutputText=":-) "
-                latin:keyLabelOption="hasPopupHint"
+                latin:keyLabelFlags="hasPopupHint"
                 latin:moreKeys="@string/more_keys_for_smiley"
                 latin:maxMoreKeysColumn="5"
                 latin:backgroundType="functional" />
@@ -96,7 +96,8 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_go_key"
-                latin:keyLabelOption="autoXScale"
+                latin:keyLabelFlags="autoXScale"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="action" />
         </case>
         <case
@@ -106,7 +107,8 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_next_key"
-                latin:keyLabelOption="autoXScale"
+                latin:keyLabelFlags="autoXScale"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="action" />
         </case>
         <case
@@ -116,7 +118,8 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_done_key"
-                latin:keyLabelOption="autoXScale"
+                latin:keyLabelFlags="autoXScale"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="action" />
         </case>
         <case
@@ -126,7 +129,8 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_send_key"
-                latin:keyLabelOption="autoXScale"
+                latin:keyLabelFlags="autoXScale"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="action" />
         </case>
         <case
@@ -136,6 +140,8 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyIcon="iconSearchKey"
+                latin:keyLabelFlags="autoXScale"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="action" />
         </case>
         <default>
@@ -143,22 +149,29 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyIcon="iconReturnKey"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="functional" />
         </default>
     </switch>
     <key-style
         latin:styleName="spaceKeyStyle"
         latin:code="@integer/key_space"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="shortcutKeyStyle"
         latin:code="@integer/key_shortcut"
         latin:keyIcon="iconShortcutKey"
+        latin:keyIconDisabled="iconDisabledShortcutKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:parentStyle="f1PopupStyle" />
     <key-style
         latin:styleName="settingsKeyStyle"
         latin:code="@integer/key_settings"
         latin:keyIcon="iconSettingsKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="tabKeyStyle"
@@ -186,7 +199,8 @@
                 latin:code="@integer/key_switch_alpha_symbol"
                 latin:keyIcon="iconShortcutForLabel"
                 latin:keyLabel="@string/label_to_symbol_with_microphone_key"
-                latin:keyLabelOption="withIconRight"
+                latin:keyLabelFlags="withIconRight"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="functional" />
         </case>
         <default>
@@ -194,6 +208,7 @@
                 latin:styleName="toSymbolKeyStyle"
                 latin:code="@integer/key_switch_alpha_symbol"
                 latin:keyLabel="@string/label_to_symbol_key"
+                latin:keyActionFlags="noKeyPreview"
                 latin:backgroundType="functional" />
         </default>
     </switch>
@@ -201,22 +216,25 @@
         latin:styleName="toAlphaKeyStyle"
         latin:code="@integer/key_switch_alpha_symbol"
         latin:keyLabel="@string/label_to_alpha_key"
+        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:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="backFromMoreSymbolKeyStyle"
         latin:code="@integer/key_shift"
         latin:keyLabel="@string/label_to_symbol_key"
+        latin:keyActionFlags="noKeyPreview"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="punctuationKeyStyle"
         latin:keyLabel="."
         latin:keyHintLabel="@string/keyhintlabel_for_punctuation"
-        latin:keyLabelOption="hasPopupHint"
+        latin:keyLabelFlags="hasPopupHint"
         latin:moreKeys="@string/more_keys_for_punctuation"
         latin:maxMoreKeysColumn="@integer/mini_keyboard_column_for_punctuation"
         latin:backgroundType="functional" />
diff --git a/java/res/xml/kbd_numkey_styles.xml b/java/res/xml/kbd_numkey_styles.xml
index 5d54399..003165d 100644
--- a/java/res/xml/kbd_numkey_styles.xml
+++ b/java/res/xml/kbd_numkey_styles.xml
@@ -22,18 +22,24 @@
     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"
@@ -100,7 +106,24 @@
         latin:keyLabel="@string/label_to_phone_numeric_key"
         latin:parentStyle="numModeKeyStyle" />
     <key-style
+        latin:styleName="numPauseKeyStyle"
+        latin:code="44"
+        latin:keyLabel="@string/label_pause_key"
+        latin:keyLabelFlags="followKeyHintLabelRatio|autoXScale"
+        latin:parentStyle="numKeyBaseStyle" />
+    <key-style
+        latin:styleName="numWaitKeyStyle"
+        latin:code="59"
+        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:parentStyle="numKeyBaseStyle" />
 </merge>
diff --git a/java/res/xml/kbd_qwerty.xml b/java/res/xml/kbd_qwerty.xml
index 40917b9..ae0146c 100644
--- a/java/res/xml/kbd_qwerty.xml
+++ b/java/res/xml/kbd_qwerty.xml
@@ -20,7 +20,6 @@
 
 <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" />
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml/kbd_qwertz.xml
similarity index 95%
rename from java/res/xml-hu/kbd_qwerty.xml
rename to java/res/xml/kbd_qwertz.xml
index 3195d5b..5bcceb2 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml/kbd_qwertz.xml
@@ -20,7 +20,6 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="hu"
 >
     <include
         latin:keyboardLayout="@xml/kbd_rows_qwertz" />
diff --git a/java/res/xml/kbd_rows_russian.xml b/java/res/xml/kbd_rows_bulgarian.xml
similarity index 72%
copy from java/res/xml/kbd_rows_russian.xml
copy to java/res/xml/kbd_rows_bulgarian.xml
index f1794e7..7b18361 100644
--- a/java/res/xml/kbd_rows_russian.xml
+++ b/java/res/xml/kbd_rows_bulgarian.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.
@@ -27,102 +27,97 @@
         latin:keyWidth="9.091%p"
     >
         <Key
-            latin:keyLabel="й"
+            latin:keyLabel="ч"
             latin:keyHintLabel="1"
             latin:moreKeys="1" />
         <Key
-            latin:keyLabel="ц"
+            latin:keyLabel="ш"
             latin:keyHintLabel="2"
             latin:moreKeys="2" />
         <Key
-            latin:keyLabel="у"
+            latin:keyLabel="е"
             latin:keyHintLabel="3"
             latin:moreKeys="3" />
         <Key
-            latin:keyLabel="к"
+            latin:keyLabel="р"
             latin:keyHintLabel="4"
             latin:moreKeys="4" />
         <Key
-            latin:keyLabel="е"
+            latin:keyLabel="т"
             latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
+            latin:moreKeys="5" />
         <Key
-            latin:keyLabel="н"
+            latin:keyLabel="ъ"
             latin:keyHintLabel="6"
             latin:moreKeys="6" />
         <Key
-            latin:keyLabel="г"
+            latin:keyLabel="у"
             latin:keyHintLabel="7"
             latin:moreKeys="7" />
         <Key
-            latin:keyLabel="ш"
+            latin:keyLabel="и"
             latin:keyHintLabel="8"
-            latin:moreKeys="8" />
+            latin:moreKeys="8,ѝ" />
         <Key
-            latin:keyLabel="щ"
+            latin:keyLabel="о"
             latin:keyHintLabel="9"
             latin:moreKeys="9" />
         <Key
-            latin:keyLabel="з"
+            latin:keyLabel="п"
             latin:keyHintLabel="0"
             latin:moreKeys="0" />
         <Key
-            latin:keyLabel="х"
-            latin:moreKeys="@string/more_keys_for_cyrillic_ha"
+            latin:keyLabel="я"
             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="м" />
+            latin:keyLabel="д" />
         <Key
-            latin:keyLabel="и" />
+            latin:keyLabel="ф" />
         <Key
-            latin:keyLabel="т" />
+            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" />
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="13.636%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:keyStyle="deleteKeyStyle"
diff --git a/java/res/xml/kbd_rows_phone_shift.xml b/java/res/xml/kbd_rows_phone_shift.xml
index 3c283d3..b39e2da 100644
--- a/java/res/xml/kbd_rows_phone_shift.xml
+++ b/java/res/xml/kbd_rows_phone_shift.xml
@@ -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" />
diff --git a/java/res/xml/kbd_rows_scandinavian.xml b/java/res/xml/kbd_rows_scandinavian.xml
index 4f138c5..eb32c00 100644
--- a/java/res/xml/kbd_rows_scandinavian.xml
+++ b/java/res/xml/kbd_rows_scandinavian.xml
@@ -67,7 +67,7 @@
             latin:keyHintLabel="0"
             latin:moreKeys="@string/more_keys_for_p" />
         <Key
-            latin:keyLabel="å"
+            latin:keyLabel="@string/keylabel_for_scandinavia_row1_11"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
@@ -75,8 +75,7 @@
     >
         <Key
             latin:keyLabel="a"
-            latin:moreKeys="@string/more_keys_for_a"
-            latin:keyWidth="8.75%p" />
+            latin:moreKeys="@string/more_keys_for_a" />
         <Key
             latin:keyLabel="s"
             latin:moreKeys="@string/more_keys_for_s" />
diff --git a/java/res/xml/kbd_rows_russian.xml b/java/res/xml/kbd_rows_slavic.xml
similarity index 81%
rename from java/res/xml/kbd_rows_russian.xml
rename to java/res/xml/kbd_rows_slavic.xml
index f1794e7..426be72 100644
--- a/java/res/xml/kbd_rows_russian.xml
+++ b/java/res/xml/kbd_rows_slavic.xml
@@ -37,7 +37,7 @@
         <Key
             latin:keyLabel="у"
             latin:keyHintLabel="3"
-            latin:moreKeys="3" />
+            latin:moreKeys="@string/more_keys_for_slavic_u" />
         <Key
             latin:keyLabel="к"
             latin:keyHintLabel="4"
@@ -45,11 +45,11 @@
         <Key
             latin:keyLabel="е"
             latin:keyHintLabel="5"
-            latin:moreKeys="@string/more_keys_for_cyrillic_e" />
+            latin:moreKeys="@string/more_keys_for_slavic_ye" />
         <Key
             latin:keyLabel="н"
             latin:keyHintLabel="6"
-            latin:moreKeys="6" />
+            latin:moreKeys="@string/more_keys_for_slavic_en" />
         <Key
             latin:keyLabel="г"
             latin:keyHintLabel="7"
@@ -59,7 +59,7 @@
             latin:keyHintLabel="8"
             latin:moreKeys="8" />
         <Key
-            latin:keyLabel="щ"
+            latin:keyLabel="@string/keylabel_for_slavic_shcha"
             latin:keyHintLabel="9"
             latin:moreKeys="9" />
         <Key
@@ -68,17 +68,17 @@
             latin:moreKeys="0" />
         <Key
             latin:keyLabel="х"
-            latin:moreKeys="@string/more_keys_for_cyrillic_ha"
+            latin:moreKeys="@string/more_keys_for_slavic_ha"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
             latin:keyWidth="9.091%p"
     >
         <Key
-            latin:keyLabel="ф"
-            latin:keyWidth="8.75%p" />
+            latin:keyLabel="ф" />
         <Key
-            latin:keyLabel="ы" />
+            latin:keyLabel="@string/keylabel_for_slavic_yery"
+            latin:moreKeys="@string/more_keys_for_slavic_yery" />
         <Key
             latin:keyLabel="в" />
         <Key
@@ -88,7 +88,8 @@
         <Key
             latin:keyLabel="р" />
         <Key
-            latin:keyLabel="о" />
+            latin:keyLabel="о"
+            latin:moreKeys="@string/more_keys_for_slavic_o" />
         <Key
             latin:keyLabel="л" />
         <Key
@@ -114,12 +115,12 @@
         <Key
             latin:keyLabel="м" />
         <Key
-            latin:keyLabel="и" />
+            latin:keyLabel="@string/keylabel_for_slavic_i" />
         <Key
             latin:keyLabel="т" />
         <Key
             latin:keyLabel="ь"
-            latin:moreKeys="@string/more_keys_for_cyrillic_soft_sign" />
+            latin:moreKeys="@string/more_keys_for_slavic_soft_sign" />
         <Key
             latin:keyLabel="б" />
         <Key
diff --git a/java/res/xml-fi/kbd_qwerty.xml b/java/res/xml/kbd_scandinavian.xml
similarity index 95%
rename from java/res/xml-fi/kbd_qwerty.xml
rename to java/res/xml/kbd_scandinavian.xml
index 75721e0..4caa989 100644
--- a/java/res/xml-fi/kbd_qwerty.xml
+++ b/java/res/xml/kbd_scandinavian.xml
@@ -20,7 +20,6 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="fi"
 >
     <include
         latin:keyboardLayout="@xml/kbd_rows_scandinavian" />
diff --git a/java/res/xml-sr/kbd_qwerty.xml b/java/res/xml/kbd_serbian.xml
similarity index 90%
rename from java/res/xml-sr/kbd_qwerty.xml
rename to java/res/xml/kbd_serbian.xml
index 58fc187..40b7199 100644
--- a/java/res/xml-sr/kbd_qwerty.xml
+++ b/java/res/xml/kbd_serbian.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, 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.
@@ -20,7 +20,6 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="sr"
 >
     <include
         latin:keyboardLayout="@xml/kbd_rows_serbian" />
diff --git a/java/res/xml/kbd_settings_or_tab.xml b/java/res/xml/kbd_settings_or_tab.xml
index 4a8bcc7..2d35e3b 100644
--- a/java/res/xml/kbd_settings_or_tab.xml
+++ b/java/res/xml/kbd_settings_or_tab.xml
@@ -25,8 +25,11 @@
         <case
             latin:hasSettingsKey="true"
         >
+            <!-- Because this settings key is not adjacent to the space key, this key should be
+                 just ignored while typing (altCode=CODE_UNSPECIFIED). -->
             <Key
                 latin:keyStyle="settingsKeyStyle"
+                latin:altCode="@integer/key_unspecified"
                 latin:keyWidth="9.2%p" />
         </case>
         <!-- hasSettingsKey="false" -->
diff --git a/java/res/xml-de-rZZ/kbd_qwerty.xml b/java/res/xml/kbd_slavic.xml
similarity index 89%
rename from java/res/xml-de-rZZ/kbd_qwerty.xml
rename to java/res/xml/kbd_slavic.xml
index d5fd8ef..6207d29 100644
--- a/java/res/xml-de-rZZ/kbd_qwerty.xml
+++ b/java/res/xml/kbd_slavic.xml
@@ -4,7 +4,7 @@
 **
 ** Copyright 2011, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License");
+** Licensed under the Apache License, Version 2.0 (the "License"):
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 **
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLocale="de"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_rows_qwerty" />
+        latin:keyboardLayout="@xml/kbd_rows_slavic" />
 </Keyboard>
diff --git a/java/res/xml-es/kbd_qwerty.xml b/java/res/xml/kbd_spanish.xml
similarity index 95%
rename from java/res/xml-es/kbd_qwerty.xml
rename to java/res/xml/kbd_spanish.xml
index 568f4d6..cfb8c6c 100644
--- a/java/res/xml-es/kbd_qwerty.xml
+++ b/java/res/xml/kbd_spanish.xml
@@ -20,7 +20,6 @@
 
 <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" />
diff --git a/java/res/xml/kbd_symbols_shift_row4.xml b/java/res/xml/kbd_symbols_shift_row4.xml
index 89e80e5..079112c 100644
--- a/java/res/xml/kbd_symbols_shift_row4.xml
+++ b/java/res/xml/kbd_symbols_shift_row4.xml
@@ -33,7 +33,7 @@
                     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:keyLabelFlags="hasPopupHint" -->
                     <!-- latin:moreKeys="‟" -->
                 <Key
                     latin:keyLabel="„"
@@ -56,7 +56,7 @@
                 <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:keyLabelFlags="hasPopupHint" -->
                     <!-- latin:moreKeys="‟" -->
                 <Key
                     latin:keyLabel="„"
diff --git a/java/res/xml/keyboard_set.xml b/java/res/xml/keyboard_set.xml
new file mode 100644
index 0000000..96f352b
--- /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="symbolsShift"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneShift"
+        latin:elementKeyboard="@xml/kbd_phone_shift" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 6184add..650f91b 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -20,7 +20,8 @@
 <!-- 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 -->
+<!-- Keyboard: en_US, en_GB, ar, be, bg, cs, da, de, de(QWERTY), es, es_US, et, fi, fr, fr_CA,
+     fr_CH, hr, hu, it, iw, ky, lt, lv, nb, nl, pl, pt, ro, ru, sk, sl, sr, sv, tr, uk, vi -->
 <!-- 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 +32,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 +48,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,7 +77,7 @@
             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"
@@ -77,6 +90,12 @@
     />
     <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="fi"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
@@ -126,6 +145,24 @@
     />
     <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="nb"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
@@ -150,12 +187,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 +227,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_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 80613a5..f38b85f 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -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/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..b48dc52 100644
--- a/java/res/xml/spellchecker.xml
+++ b/java/res/xml/spellchecker.xml
@@ -21,7 +21,8 @@
 <!-- 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/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/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 4666332..9caed00 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.os.SystemClock;
@@ -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,7 +73,7 @@
         // 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);
@@ -120,8 +119,8 @@
      * 
      * @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 +136,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 +170,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..d834dd1 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -82,8 +81,8 @@
         }
     }
 
-    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,7 +93,7 @@
         // 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);
@@ -125,8 +124,6 @@
      */
     @Override
     public void onFlickGesture(int direction) {
-        final int keyEventCode;
-
         switch (direction) {
         case FlickGestureDetector.FLICK_LEFT:
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index cef8226..9141daa 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -17,7 +17,6 @@
 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;
@@ -29,7 +28,6 @@
 import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
 import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
 import com.android.inputmethod.keyboard.PointerTracker;
 
@@ -42,10 +40,10 @@
     private LatinKeyboardView mView;
     private AccessibleKeyboardActionListener mListener;
 
-    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();
     }
 
@@ -61,7 +59,7 @@
         // 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,8 +70,7 @@
         mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
     }
 
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
-            PointerTracker tracker) {
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (mView == null) {
             Log.e(TAG, "No keyboard view set!");
             return false;
@@ -81,7 +78,7 @@
 
         switch (event.getEventType()) {
         case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
-            final Key key = tracker.getKey(mLastHoverKeyIndex);
+            final Key key = mLastHoverKey;
 
             if (key == null)
                 break;
@@ -130,12 +127,12 @@
         switch (event.getAction()) {
         case MotionEventCompatUtils.ACTION_HOVER_ENTER:
         case MotionEventCompatUtils.ACTION_HOVER_MOVE:
-            final int keyIndex = tracker.getKeyIndexOn(x, y);
+            final Key key = tracker.getKeyOn(x, y);
 
-            if (keyIndex != mLastHoverKeyIndex) {
-                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
-                mLastHoverKeyIndex = keyIndex;
-                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
+            if (key != mLastHoverKey) {
+                fireKeyHoverEvent(mLastHoverKey, false);
+                mLastHoverKey = key;
+                fireKeyHoverEvent(mLastHoverKey, true);
             }
 
             return true;
@@ -144,7 +141,7 @@
         return false;
     }
 
-    private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
+    private void fireKeyHoverEvent(Key key, boolean entering) {
         if (mListener == null) {
             Log.e(TAG, "No accessible keyboard action listener set!");
             return;
@@ -155,11 +152,6 @@
             return;
         }
 
-        if (keyIndex == KeyDetector.NOT_A_KEY)
-            return;
-
-        final Key key = tracker.getKey(keyIndex);
-
         if (key == null)
             return;
 
diff --git a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
index 9d99e31..db12f76 100644
--- a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
+++ b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
@@ -126,7 +126,6 @@
         }
 
         final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event);
-        final long timeout = event.getEventTime() - mCachedHoverEnter.getEventTime();
 
         switch (event.getAction()) {
         case MotionEventCompatUtils.ACTION_HOVER_MOVE:
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 7302830..efaf58f 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.Key;
@@ -45,8 +44,8 @@
     // 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() {
@@ -60,7 +59,7 @@
         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);
 
@@ -136,16 +135,14 @@
         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);
         }
 
@@ -187,11 +184,14 @@
      * @return the key code for the specified key
      */
     private int getCorrectKeyCode(Keyboard keyboard, Key key) {
-        if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLabel)) {
+        // 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 (keyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()
+                && !TextUtils.isEmpty(key.mHintLabel)) {
             return key.mHintLabel.charAt(0);
-        } else {
-            return key.mCode;
         }
+        return key.mCode;
     }
 
     /**
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..e1db360 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -26,12 +26,16 @@
             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);
 
@@ -47,6 +51,12 @@
         return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0;
     }
 
+    public static boolean hasFlagForceAscii(int imeOptions) {
+        if (OBJ_IME_FLAG_FORCE_ASCII == null)
+            return false;
+        return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0;
+    }
+
     public static void performEditorActionNext(InputConnection ic) {
         ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
     }
@@ -93,10 +103,19 @@
                 break;
             }
         }
+        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 flags + action;
     }
 }
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 51dc4cd..0e5f8c8 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -153,8 +153,7 @@
         return Utils.getInputMethodInfo(this, 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();
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 161ef09..f476d83 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
@@ -94,7 +94,7 @@
         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 +106,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/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index 3f8c2ef..a013ebc 100644
--- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -695,12 +695,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)
+                LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)
                 || Utils.inPrivateImeOptions(mService.getPackageName(),
-                        LatinIME.IME_OPTION_NO_MICROPHONE, attribute);
+                        LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo);
         return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
                 && SpeechRecognizer.isRecognitionAvailable(mService);
     }
@@ -709,7 +709,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 +723,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));
     }
 
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
index dbe7aec..e75e148 100644
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.deprecated.languageswitcher;
 
 import com.android.inputmethod.compat.SharedPreferencesCompat;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.KeyboardSet;
 import com.android.inputmethod.latin.DictionaryFactory;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
@@ -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)) {
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/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f1ae0b3..3a9423f 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -22,19 +22,20 @@
 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.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.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -42,36 +43,42 @@
  * 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;
     /** 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;
+    /** 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;
+    private 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_UPPERCASE_LETTER = 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;
 
+    // TODO: These icon references could be int (icon attribute id)
     /** Icon to display instead of a label. Icon takes precedence over a label */
     private Drawable mIcon;
+    /** Icon for disabled state */
+    private Drawable mDisabledIcon;
     /** Preview version of the icon, for the preview popup */
-    private Drawable mPreviewIcon;
+    public final Drawable mPreviewIcon;
 
     /** Width of the key, not including the gap */
     public final int mWidth;
@@ -94,7 +101,7 @@
     /** Text to output when pressed. This can be multiple characters, like ".com" */
     public final CharSequence mOutputText;
     /** More keys */
-    public final CharSequence[] mMoreKeys;
+    public final String[] mMoreKeys;
     /** More keys maximum column number */
     public final int mMaxMoreKeysColumn;
 
@@ -105,8 +112,12 @@
     public static final int BACKGROUND_TYPE_ACTION = 2;
     public static final int BACKGROUND_TYPE_STICKY = 3;
 
-    /** 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 final int mHashCode;
 
     /** The current pressed state of this key */
     private boolean mPressed;
@@ -114,8 +125,6 @@
     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>();
@@ -151,19 +160,24 @@
         }
     }
 
-    private static int getCode(Resources res, KeyboardParams params, String moreKeySpec) {
+    private static int getCode(Resources res, Keyboard.Params 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));
+    private static Drawable getIcon(Keyboard.Params params, String moreKeySpec) {
+        final int iconAttrId = MoreKeySpecParser.getIconAttrId(moreKeySpec);
+        if (iconAttrId == KeyboardIconsSet.ICON_UNDEFINED) {
+            return null;
+        } else {
+            return params.mIconsSet.getIconByAttrId(iconAttrId);
+        }
     }
 
     /**
      * This constructor is being used only for key in more keys keyboard.
      */
-    public Key(Resources res, KeyboardParams params, String moreKeySpec,
+    public Key(Resources res, Keyboard.Params 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),
@@ -173,7 +187,7 @@
     /**
      * This constructor is being used only for key in popup suggestions pane.
      */
-    public Key(KeyboardParams params, CharSequence label, CharSequence hintLabel, Drawable icon,
+    public Key(Keyboard.Params params, CharSequence label, CharSequence hintLabel, Drawable icon,
             int code, CharSequence outputText, int x, int y, int width, int height) {
         mHeight = height - params.mVerticalGap;
         mHorizontalGap = params.mHorizontalGap;
@@ -181,19 +195,24 @@
         mVisualInsetsLeft = mVisualInsetsRight = 0;
         mWidth = width - mHorizontalGap;
         mHintLabel = hintLabel;
-        mLabelOption = 0;
+        mLabelFlags = 0;
         mBackgroundType = BACKGROUND_TYPE_NORMAL;
-        mRepeatable = false;
+        mActionFlags = 0;
         mMoreKeys = null;
         mMaxMoreKeysColumn = 0;
         mLabel = label;
         mOutputText = outputText;
         mCode = code;
+        mAltCode = Keyboard.CODE_UNSPECIFIED;
         mIcon = icon;
+        mDisabledIcon = null;
+        mPreviewIcon = null;
         // 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 +224,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 +241,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,7 +260,7 @@
         // Update row to have current x coordinate.
         row.setXPos(keyXPos + keyWidth);
 
-        final CharSequence[] moreKeys = style.getTextArray(keyAttr,
+        final String[] 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.
@@ -254,58 +275,110 @@
 
         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);
+        mActionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags, 0);
 
         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,
+        final int previewIconAttrId = KeyboardIconsSet.getIconAttrId(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);
-        }
+        mPreviewIcon = iconsSet.getIconByAttrId(previewIconAttrId);
+        final int iconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED));
+        mIcon = iconsSet.getIconByAttrId(iconAttrId);
+        final int disabledIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED));
+        mDisabledIcon = iconsSet.getIconByAttrId(disabledIconAttrId);
         mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
 
         mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
-        mLabelOption = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption, 0);
+        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags, 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)) {
+        if (code == Keyboard.CODE_UNSPECIFIED && mOutputText == null
+                && !TextUtils.isEmpty(mLabel)) {
+            if (mLabel.length() != 1) {
+                Log.w(TAG, "Label is not a single letter: label=" + mLabel);
+            }
             final int firstChar = mLabel.charAt(0);
             mCode = getRtlParenthesisCode(firstChar, params.mIsRtlKeyboard);
-        } else if (code != Keyboard.CODE_UNSPECIFIED) {
-            mCode = code;
+        } else if (code == Keyboard.CODE_UNSPECIFIED && mOutputText != null) {
+            mCode = Keyboard.CODE_OUTPUT_TEXT;
         } else {
-            mCode = Keyboard.CODE_DUMMY;
+            mCode = code;
         }
+        mAltCode = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED);
+        mHashCode = hashCode(this);
 
         keyAttr.recycle();
     }
 
-    public void markAsLeftEdge(KeyboardParams params) {
+    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 can be distinguishable without the following members.
+                // key.mAltCode,
+                // key.mOutputText,
+                // key.mActionFlags,
+                // key.mLabelFlags,
+                // key.mIcon,
+                // key.mPreviewIcon,
+                // key.mBackgroundType,
+                // key.mHorizontalGap,
+                // key.mVerticalGap,
+                // key.mVisualInsetLeft,
+                // key.mVisualInsetRight,
+                // Arrays.hashCode(key.mMoreKeys),
+                // 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);
+    }
+
+    @Override
+    public int hashCode() {
+        return mHashCode;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof Key && equals((Key)o);
+    }
+
+    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;
     }
 
@@ -317,11 +390,31 @@
         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 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;
@@ -330,12 +423,12 @@
 
     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) {
+                && (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,65 +436,50 @@
     }
 
     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;
-    }
-
-    public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) {
-        mNeedsSpecialPopupHint = needsSpecialPopupHint;
-    }
-
-    public boolean needsSpecialPopupHint() {
-        return mNeedsSpecialPopupHint;
+        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
     }
 
     public boolean hasUppercaseLetter() {
-        return (mLabelOption & LABEL_OPTION_HAS_UPPERCASE_LETTER) != 0;
+        return (mLabelFlags & LABEL_FLAGS_HAS_UPPERCASE_LETTER) != 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;
+        return mEnabled ? mIcon : mDisabledIcon;
     }
 
-    public Drawable getPreviewIcon() {
-        return mPreviewIcon;
-    }
-
+    // TODO: Get rid of this method.
     public void setIcon(Drawable icon) {
         mIcon = icon;
     }
 
-    public void setPreviewIcon(Drawable icon) {
-        mPreviewIcon = icon;
-    }
-
     /**
      * Informs the key that it has been pressed, in case it needs to change its appearance or
      * state.
@@ -436,9 +514,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);
@@ -536,16 +614,16 @@
     }
 
     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);
+        public Spacer(Keyboard.Params params, Drawable icon, int x, int y, int width, int height) {
+            super(params, null, null, icon, Keyboard.CODE_UNSPECIFIED, null, x, y, width, height);
         }
 
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 3298c41..0d27162 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -19,14 +19,12 @@
 import android.util.Log;
 
 import java.util.Arrays;
-import java.util.List;
 
 public class KeyDetector {
     private static final String TAG = KeyDetector.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     public static final int NOT_A_CODE = -1;
-    public static final int NOT_A_KEY = -1;
 
     private final int mKeyHysteresisDistanceSquared;
 
@@ -39,7 +37,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.
@@ -96,22 +94,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 #getKeyAndNearbyCodes}.
      *
-     * @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 #getKeyAndNearbyCodes}.
      */
     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 #getKeyAndNearbyCodes}
      * 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 #getKeyAndNearbyCodes} method. All elements in the returned array are
      *         initialized by {@link #NOT_A_CODE} value.
      */
     public int[] newCodeArray() {
@@ -122,7 +120,7 @@
 
     private void initializeNearbyKeys() {
         Arrays.fill(mDistances, Integer.MAX_VALUE);
-        Arrays.fill(mIndices, NOT_A_KEY);
+        Arrays.fill(mNeighborKeys, null);
     }
 
     /**
@@ -130,14 +128,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,11 +143,11 @@
                 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;
             }
         }
@@ -157,21 +155,20 @@
     }
 
     private void getNearbyKeyCodes(final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
-        final int[] indices = mIndices;
+        final Key[] neighborKeys = mNeighborKeys;
 
         // 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 < allCodes.length; 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;
@@ -180,31 +177,30 @@
     }
 
     /**
-     * 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
+     * @return The nearest key
      */
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    public Key getKeyAndNearbyCodes(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;
+                }
             }
         }
 
@@ -212,12 +208,25 @@
             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));
+                        + " primary=" + printableCode(primaryKey)
+                        + " codes=" + printableCodes(allCodes));
             }
         }
 
-        return primaryIndex;
+        return primaryKey;
+    }
+
+    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();
+        for (final int code : codes) {
+            if (code == NOT_A_CODE) break;
+            if (sb.length() > 0) sb.append(", ");
+            sb.append(code);
+        }
+        return "[" + sb + "]";
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 4578507..5816e56 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,15 +16,33 @@
 
 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.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.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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -47,7 +65,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 = ' ';
@@ -64,20 +86,20 @@
     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_OUTPUT_TEXT = -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;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -99;
+    public static final int CODE_UNSPECIFIED = -9;
 
     public final KeyboardId mId;
     public final int mThemeId;
@@ -105,18 +127,19 @@
     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> mKeys;
+    public final Set<Key> mShiftKeys;
     public final Set<Key> mShiftLockKeys;
-    public final Map<Key, Drawable> mShiftedIcons;
-    public final Map<Key, Drawable> mUnshiftedIcons;
     public final KeyboardIconsSet mIconsSet;
 
-    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
+    private final Map<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
 
     private final ProximityInfo mProximityInfo;
 
-    public Keyboard(KeyboardParams params) {
+    // TODO: Remove this variable.
+    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
+
+    public Keyboard(Params params) {
         mId = params.mId;
         mThemeId = params.mThemeId;
         mOccupiedHeight = params.mOccupiedHeight;
@@ -130,11 +153,9 @@
         mTopPadding = params.mTopPadding;
         mVerticalGap = params.mVerticalGap;
 
-        mKeys = Collections.unmodifiableList(params.mKeys);
-        mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
+        mKeys = Collections.unmodifiableSet(params.mKeys);
+        mShiftKeys = Collections.unmodifiableSet(params.mShiftKeys);
         mShiftLockKeys = Collections.unmodifiableSet(params.mShiftLockKeys);
-        mShiftedIcons = Collections.unmodifiableMap(params.mShiftedIcons);
-        mUnshiftedIcons = Collections.unmodifiableMap(params.mUnshiftedIcons);
         mIconsSet = params.mIconsSet;
 
         mProximityInfo = new ProximityInfo(
@@ -146,102 +167,281 @@
         return mProximityInfo;
     }
 
-    public boolean hasShiftLockKey() {
+    public Key getKey(int code) {
+        if (code == CODE_UNSPECIFIED) {
+            return null;
+        }
+        final Integer keyCode = code;
+        if (mKeyCache.containsKey(keyCode)) {
+            return mKeyCache.get(keyCode);
+        }
+
+        for (final Key key : mKeys) {
+            if (key.mCode == code) {
+                mKeyCache.put(keyCode, key);
+                return key;
+            }
+        }
+        mKeyCache.put(keyCode, null);
+        return null;
+    }
+
+    // TODO: Remove this method.
+    boolean hasShiftLockKey() {
         return !mShiftLockKeys.isEmpty();
     }
 
-    public boolean setShiftLocked(boolean newShiftLockState) {
+    // TODO: Remove this method.
+    void 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));
-            }
+            final int attrId = newShiftLockState
+                    ? R.styleable.Keyboard_iconShiftKeyShifted
+                    : R.styleable.Keyboard_iconShiftKey;
+            key.setIcon(mIconsSet.getIconByAttrId(attrId));
         }
         mShiftState.setShiftLocked(newShiftLockState);
-        return true;
     }
 
+    // TODO: Move this method to KeyboardId.
     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));
-            }
+    private void setShiftKeyGraphics(boolean newShiftState) {
+        if (mShiftState.isShiftLocked()) {
+            return;
         }
-        return mShiftState.setShifted(newShiftState);
+        for (final Key key : mShiftKeys) {
+            final int attrId = newShiftState
+                    ? R.styleable.Keyboard_iconShiftKeyShifted
+                    : R.styleable.Keyboard_iconShiftKey;
+            key.setIcon(mIconsSet.getIconByAttrId(attrId));
+        }
     }
 
+    // TODO: Remove this method.
+    void setShifted(boolean newShiftState) {
+        setShiftKeyGraphics(newShiftState);
+        mShiftState.setShifted(newShiftState);
+    }
+
+    // TODO: Move this method to KeyboardId.
     public boolean isShiftedOrShiftLocked() {
         return mShiftState.isShiftedOrShiftLocked();
     }
 
-    public void setAutomaticTemporaryUpperCase() {
-        setShifted(true);
+    // TODO: Remove this method
+    void setAutomaticTemporaryUpperCase() {
+        setShiftKeyGraphics(true);
         mShiftState.setAutomaticTemporaryUpperCase();
     }
 
-    public boolean isAutomaticTemporaryUpperCase() {
-        return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase();
-    }
-
+    // TODO: Move this method to KeyboardId.
     public boolean isManualTemporaryUpperCase() {
-        return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase();
+        return mShiftState.isManualTemporaryUpperCase();
     }
 
-    public boolean isManualTemporaryUpperCaseFromAuto() {
-        return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto();
-    }
-
-    public KeyboardShiftState getKeyboardShiftState() {
-        return mShiftState;
-    }
-
-    public boolean isAlphaKeyboard() {
-        return mId.isAlphabetKeyboard();
-    }
-
-    public boolean isPhoneKeyboard() {
-        return mId.isPhoneKeyboard();
-    }
-
-    public boolean isNumberKeyboard() {
-        return mId.isNumberKeyboard();
-    }
-
+    // TODO: Remove this method.
     public CharSequence adjustLabelCase(CharSequence label) {
-        if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3
-                && Character.isLowerCase(label.charAt(0))) {
+        if (mId.isAlphabetKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label)
+                && label.length() < 3 && Character.isLowerCase(label.charAt(0))) {
             return label.toString().toUpperCase(mId.mLocale);
         }
         return label;
     }
 
-    /**
-     * Returns the indices 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
-     * 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 static boolean isLetterCode(int code) {
+        return code >= MINIMUM_LETTER_CODE;
     }
 
-    public static String themeName(int themeId) {
+    public static class Params {
+        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 Set<Key> mKeys = new HashSet<Key>();
+        public final Set<Key> mShiftKeys = new HashSet<Key>();
+        public final Set<Key> mShiftLockKeys = new HashSet<Key>();
+        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();
+            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);
+                }
+            }
+        }
+
+        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;
+            }
+        }
+    }
+
+    /**
+     * 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 the nearest keys to the given point. If the given
+     * point is out of range, then an array of size zero is returned.
+     */
+    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 printableCode(int code) {
+        switch (code) {
+        case CODE_SHIFT: return "shift";
+        case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
+        case CODE_CAPSLOCK: return "capslock";
+        case CODE_OUTPUT_TEXT: return "text";
+        case CODE_DELETE: return "delete";
+        case CODE_SHORTCUT: return "shortcut";
+        case CODE_UNSPECIFIED: return "unspec";
+        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);
+        }
+    }
+
+    public static String toThemeName(int themeId) {
         // This should be aligned with theme-*.xml resource files' themeId attribute.
         switch (themeId) {
         case 0: return "Basic";
@@ -253,4 +453,812 @@
         default: return null;
         }
     }
+
+    /**
+     * 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 TAG = Builder.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 Params 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, 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();
+
+                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 = 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, 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);
+
+            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)
+                    throw new RuntimeException("touchPositionCorrectionData is not defined");
+                return;
+            }
+
+            final String[] data = context.getResources().getStringArray(resourceId);
+            params.mTouchPositionCorrection.load(data);
+        }
+
+        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(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(XmlPullParser 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 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.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 XmlParseUtils.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 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 (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 XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+                    }
+                }
+            }
+        }
+
+        private void parseKey(XmlPullParser parser, Row row, boolean skip)
+                throws XmlPullParserException, IOException {
+            if (skip) {
+                XmlParseUtils.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)));
+                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);
+            } else {
+                final Key.Spacer spacer = new Key.Spacer(
+                        mResources, mParams, row, parser, mKeyStyles);
+                if (DEBUG) Log.d(TAG, String.format("<%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);
+            } else {
+                final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                        R.styleable.Keyboard_Include);
+                int keyboardLayout = 0;
+                try {
+                    XmlParseUtils.checkAttributeExists(a,
+                            R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
+                            TAG_INCLUDE, parser);
+                    keyboardLayout = a.getResourceId(
+                            R.styleable.Keyboard_Include_keyboardLayout, 0);
+                } finally {
+                    a.recycle();
+                }
+
+                XmlParseUtils.checkEndTag(TAG_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 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) 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 XmlParseUtils.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 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 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.navigateAction());
+                final boolean passwordInputMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
+                final boolean hasSettingsKeyMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_hasSettingsKey, id.hasSettingsKey());
+                final boolean f2KeyModeMatched = matchInteger(a,
+                        R.styleable.Keyboard_Case_f2KeyMode, id.f2KeyMode());
+                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.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 = 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)
+                throws XmlPullParserException {
+            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 (!skip)
+                    mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+            } finally {
+                keyStyleAttr.recycle();
+                keyAttrs.recycle();
+            }
+        }
+
+        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..dce2c37 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,7 +35,7 @@
      * @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.
@@ -79,9 +77,9 @@
 
     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) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 2e4988f..d95c3b3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -16,15 +16,16 @@
 
 package com.android.inputmethod.keyboard;
 
+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;
 
+// TODO: Move to com.android.inputmethod.keyboard.internal package.
 /**
  * Represents the parameters necessary to construct a new LatinKeyboard,
  * which also serve as a unique identifier for each keyboard type.
@@ -37,104 +38,143 @@
     public static final int MODE_PHONE = 4;
     public static final int MODE_NUMBER = 5;
 
-    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;
+    /* TODO: Implement alphabet variant shift keyboard.
+    public static final int ELEMENT_ALPHABET_MANUAL_TEMPORARY_SHIFT = 1;
+    public static final int ELEMENT_ALPHABET_AUTOMATIC_TEMPORARY_SHIFT = 2;
+    public static final int ELEMENT_ALPHABET_SHIFT_LOCK = 3;
+    public static final int ELEMENT_ALPHABET_SHIFT_LOCK_SHIFT = 4;
+    */
+    public static final int ELEMENT_SYMBOLS = 5;
+    public static final int ELEMENT_SYMBOLS_SHIFT = 6;
+    public static final int ELEMENT_PHONE = 7;
+    public static final int ELEMENT_PHONE_SHIFT = 8;
+    public static final int ELEMENT_NUMBER = 9;
+
+    private static final int F2KEY_MODE_NONE = 0;
+    private static final int F2KEY_MODE_SETTINGS = 1;
+    private static final int F2KEY_MODE_SHORTCUT_IME = 2;
+    private static final int F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS = 3;
 
     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 mElementState;
+    private final int mInputType;
+    private final int mImeOptions;
+    private final boolean mSettingsKeyEnabled;
     public final boolean mClobberSettingsKey;
     public final boolean mShortcutKeyEnabled;
     public final boolean mHasShortcutKey;
-    public final int mImeAction;
-
-    public final String mXmlName;
-    public final EditorInfo mAttribute;
 
     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 elementState, Locale locale, int orientation, int width, int mode,
+            int inputType, int imeOptions, boolean settingsKeyEnabled, boolean clobberSettingsKey,
+            boolean shortcutKeyEnabled, boolean hasShortcutKey) {
         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.mElementState = elementState;
+        this.mInputType = inputType;
+        this.mImeOptions = imeOptions;
+        this.mSettingsKeyEnabled = settingsKeyEnabled;
         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.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.mElementState,
+                id.mMode,
+                id.mWidth,
+                id.navigateAction(),
+                id.passwordInput(),
+                id.mSettingsKeyEnabled,
+                id.mClobberSettingsKey,
+                id.mShortcutKeyEnabled,
+                id.mHasShortcutKey,
+                id.imeAction(),
+                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.mElementState == this.mElementState
+                && other.mMode == this.mMode
+                && other.mWidth == this.mWidth
+                && other.navigateAction() == this.navigateAction()
+                && other.passwordInput() == this.passwordInput()
+                && other.mSettingsKeyEnabled == this.mSettingsKeyEnabled
+                && other.mClobberSettingsKey == this.mClobberSettingsKey
+                && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled
+                && other.mHasShortcutKey == this.mHasShortcutKey
+                && other.imeAction() == this.imeAction()
+                && other.mLocale.equals(this.mLocale);
     }
 
     public boolean isAlphabetKeyboard() {
-        return mXmlId == R.xml.kbd_qwerty;
+        return mElementState < ELEMENT_SYMBOLS;
     }
 
     public boolean isSymbolsKeyboard() {
-        return mXmlId == R.xml.kbd_symbols || mXmlId == R.xml.kbd_symbols_shift;
+        return mElementState == ELEMENT_SYMBOLS || mElementState == ELEMENT_SYMBOLS_SHIFT;
     }
 
     public boolean isPhoneKeyboard() {
-        return mMode == MODE_PHONE;
+        return mElementState == ELEMENT_PHONE || mElementState == ELEMENT_PHONE_SHIFT;
     }
 
     public boolean isPhoneShiftKeyboard() {
-        return mXmlId == R.xml.kbd_phone_shift;
+        return mElementState == ELEMENT_PHONE_SHIFT;
     }
 
-    public boolean isNumberKeyboard() {
-        return mMode == MODE_NUMBER;
+    public boolean navigateAction() {
+        // Note: Turn off checking navigation flag to show TAB key for now.
+        boolean navigateAction = InputTypeCompatUtils.isWebInputType(mInputType);
+//                || EditorInfoCompatUtils.hasFlagNavigateNext(mImeOptions)
+//                || EditorInfoCompatUtils.hasFlagNavigatePrevious(mImeOptions);
+        return navigateAction;
+    }
+
+    public boolean passwordInput() {
+        return InputTypeCompatUtils.isPasswordInputType(mInputType)
+                || InputTypeCompatUtils.isVisiblePasswordInputType(mInputType);
+    }
+
+    public int imeAction() {
+        // We are interested only in {@link EditorInfo#IME_MASK_ACTION} enum value and
+        // {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION}.
+        return mImeOptions & (
+                EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
+    }
+
+    public boolean hasSettingsKey() {
+        return mSettingsKeyEnabled && !mClobberSettingsKey;
+    }
+
+    public int f2KeyMode() {
+        if (mClobberSettingsKey) {
+            // Never shows the Settings key
+            return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
+        }
+
+        if (mSettingsKeyEnabled) {
+            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;
+        }
     }
 
     @Override
@@ -142,22 +182,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 +189,48 @@
 
     @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]",
+                elementStateToString(mElementState),
                 mLocale,
                 (mOrientation == 1 ? "port" : "land"), mWidth,
                 modeName(mMode),
-                EditorInfoCompatUtils.imeOptionsName(mImeAction),
-                f2KeyModeName(mF2KeyMode),
+                EditorInfoCompatUtils.imeOptionsName(imeAction()),
+                f2KeyModeName(f2KeyMode()),
                 (mClobberSettingsKey ? " clobberSettingsKey" : ""),
-                (mNavigateAction ? " navigateAction" : ""),
-                (mPasswordInput ? " passwordInput" : ""),
-                (mHasSettingsKey ? " hasSettingsKey" : ""),
+                (navigateAction() ? " navigateAction" : ""),
+                (passwordInput() ? " passwordInput" : ""),
+                (hasSettingsKey() ? " hasSettingsKey" : ""),
                 (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
                 (mHasShortcutKey ? " hasShortcutKey" : "")
         );
     }
 
+    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 elementStateToString(int elementState) {
+        switch (elementState) {
+        case ELEMENT_ALPHABET: return "alphabet";
+        /* TODO: Implement alphabet variant shift keyboard.
+        case ELEMENT_ALPHABET_MANUAL_TEMPORARY_SHIFT: return "alphabetManualTemporaryShift";
+        case ELEMENT_ALPHABET_AUTOMATIC_TEMPORARY_SHIFT: return "alphabetAutomaticTemporaryShift";
+        case ELEMENT_ALPHABET_SHIFT_LOCK: return "alphabetShiftLock";
+        case ELEMENT_ALPHABET_SHIFT_LOCK_SHIFT: return "alphabetShiftLockShift";
+        */
+        case ELEMENT_SYMBOLS: return "symbols";
+        case ELEMENT_SYMBOLS_SHIFT: return "symbolsShift";
+        case ELEMENT_PHONE: return "phone";
+        case ELEMENT_PHONE_SHIFT: return "phoneShift";
+        case ELEMENT_NUMBER: return "number";
+        default: return null;
+        }
+    }
+
     public static String modeName(int mode) {
         switch (mode) {
         case MODE_TEXT: return "text";
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..2852520
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -0,0 +1,335 @@
+/*
+ * 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.util.Log;
+import android.util.Xml;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+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.Utils;
+import com.android.inputmethod.latin.XmlParseUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * This class represents a set of keyboards. Each of them represents a different keyboard
+ * specific to a keyboard state, such as alphabet, symbols, and so on.  Layouts in the same
+ * {@link KeyboardSet} are related to each other.
+ * A {@link KeyboardSet} needs to be created for each {@link android.view.inputmethod.EditorInfo}.
+ */
+public class KeyboardSet {
+    private static final String TAG = KeyboardSet.class.getSimpleName();
+    private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
+
+    private static final String TAG_KEYBOARD_SET = TAG;
+    private static final String TAG_ELEMENT = "Element";
+
+    private final Context mContext;
+    private final Params mParams;
+
+    private static class Params {
+        int mMode;
+        int mInputType;
+        int mImeOptions;
+        boolean mTouchPositionCorrectionEnabled;
+        boolean mSettingsKeyEnabled;
+        boolean mVoiceKeyEnabled;
+        boolean mVoiceKeyOnMain;
+        boolean mNoSettingsKey;
+        Locale mLocale;
+        int mOrientation;
+        int mWidth;
+        final HashMap<Integer, Integer> mElementKeyboards = new HashMap<Integer, Integer>();
+        Params() {}
+    }
+
+    private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
+            new HashMap<KeyboardId, SoftReference<Keyboard>>();
+
+    public static void clearKeyboardCache() {
+        sKeyboardCache.clear();
+    }
+
+    private KeyboardSet(Context context, Params params) {
+        mContext = context;
+        mParams = params;
+    }
+
+    public Keyboard getMainKeyboard() {
+        return getKeyboard(false, false);
+    }
+
+    public Keyboard getSymbolsKeyboard() {
+        return getKeyboard(true, false);
+    }
+
+    public Keyboard getSymbolsShiftedKeyboard() {
+        final Keyboard keyboard = getKeyboard(true, true);
+        // TODO: Remove this logic once we introduce initial keyboard shift state attribute.
+        // Symbol shift keyboard may have a shift 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 shift key or not.
+        keyboard.setShiftLocked(keyboard.hasShiftLockKey());
+        return keyboard;
+    }
+
+    private Keyboard getKeyboard(boolean isSymbols, boolean isShift) {
+        final int elementState = Builder.getElementState(mParams.mMode, isSymbols, isShift);
+        final int xmlId = mParams.mElementKeyboards.get(elementState);
+        final KeyboardId id = Builder.getKeyboardId(elementState, isSymbols, mParams);
+        final Keyboard keyboard = getKeyboard(mContext, xmlId, id);
+        return keyboard;
+    }
+
+    public KeyboardId getMainKeyboardId() {
+        final int elementState = Builder.getElementState(mParams.mMode, false, false);
+        return Builder.getKeyboardId(elementState, false, mParams);
+    }
+
+    private Keyboard getKeyboard(Context context, int xmlId, 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());
+                builder.load(xmlId, 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);
+        }
+
+        // TODO: Remove setShiftLocked and setShift calls.
+        keyboard.setShiftLocked(false);
+        keyboard.setShifted(false);
+        return keyboard;
+    }
+
+    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 = Utils.getKeyboardMode(editorInfo);
+            if (editorInfo != null) {
+                params.mInputType = editorInfo.inputType;
+                params.mImeOptions = editorInfo.imeOptions;
+            }
+            params.mNoSettingsKey = Utils.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 forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(mParams.mImeOptions)
+                    || Utils.inPrivateImeOptions(
+                            mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo);
+            mParams.mLocale = (forceAscii && !asciiCapable) ? Locale.US : inputLocale;
+            mParams.mTouchPositionCorrectionEnabled = touchPositionCorrectionEnabled;
+            return this;
+        }
+
+        public Builder setOptions(boolean settingsKeyEnabled, boolean voiceKeyEnabled,
+                boolean voiceKeyOnMain) {
+            mParams.mSettingsKeyEnabled = settingsKeyEnabled;
+            @SuppressWarnings("deprecation")
+            final boolean noMicrophone = Utils.inPrivateImeOptions(
+                    mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo)
+                    || Utils.inPrivateImeOptions(
+                            null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo);
+            mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
+            mParams.mVoiceKeyOnMain = voiceKeyOnMain;
+            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) {
+                //
+            } finally {
+                LocaleUtils.setSystemLocale(mResources, savedLocale);
+            }
+            return new KeyboardSet(mContext, mParams);
+        }
+
+        // TODO: Move this method to KeyboardSet
+        static KeyboardId getKeyboardId(int elementState, boolean isSymbols, Params params) {
+            final boolean hasShortcutKey = params.mVoiceKeyEnabled
+                    && (isSymbols != params.mVoiceKeyOnMain);
+            return new KeyboardId(elementState, params.mLocale, params.mOrientation, params.mWidth,
+                    params.mMode, params.mInputType, params.mImeOptions, params.mSettingsKeyEnabled,
+                    params.mNoSettingsKey, params.mVoiceKeyEnabled, hasShortcutKey);
+        }
+
+        // TODO: Move this method to KeyboardSet
+        static int getElementState(int mode, boolean isSymbols, boolean isShift) {
+            switch (mode) {
+            case KeyboardId.MODE_PHONE:
+                return (isSymbols && isShift)
+                        ? KeyboardId.ELEMENT_PHONE_SHIFT : KeyboardId.ELEMENT_PHONE;
+            case KeyboardId.MODE_NUMBER:
+                return KeyboardId.ELEMENT_NUMBER;
+            default:
+                if (isSymbols) {
+                    return isShift ? KeyboardId.ELEMENT_SYMBOLS_SHIFT : KeyboardId.ELEMENT_SYMBOLS;
+                }
+                return KeyboardId.ELEMENT_ALPHABET;
+            }
+        }
+
+        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.mElementKeyboards.put(elementName, elementKeyboard);
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    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..cb80d05 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,25 +27,20 @@
 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.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,
+        SharedPreferences.OnSharedPreferenceChangeListener {
     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 = {
@@ -62,99 +54,26 @@
 
     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 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,12 +88,14 @@
 
     private void initInternal(LatinIME ims, SharedPreferences prefs) {
         mInputMethodService = ims;
-        mPackageName = ims.getPackageName();
         mResources = ims.getResources();
         mPrefs = prefs;
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+        mState = new KeyboardState(this);
         setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
         prefs.registerOnSharedPreferenceChangeListener(this);
+        mForceNonDistinctMultitouch = prefs.getBoolean(
+                DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
     }
 
     private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) {
@@ -195,26 +116,45 @@
         if (mThemeIndex != themeIndex) {
             mThemeIndex = themeIndex;
             mThemeContext = new ContextThemeWrapper(context, KEYBOARD_THEMES[themeIndex]);
-            mKeyboardCache.clear();
+            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.isSettingsKeyEnabled(),
+                settingsValues.isVoiceKeyEnabled(editorInfo),
+                settingsValues.isVoiceKeyOnMain());
+        mKeyboardSet = builder.build();
+        final KeyboardId mainKeyboardId = mKeyboardSet.getMainKeyboardId();
         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();
+            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
         } catch (RuntimeException e) {
-            Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
-            LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
+            Log.w(TAG, "loading keyboard failed: " + mainKeyboardId, e);
+            LatinImeLogger.logOnException(mainKeyboardId.toString(), e);
+            return;
+        }
+        // TODO: Should get rid of this special case handling for Phone Number layouts once we
+        // have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted
+        // respectively.
+        if (mainKeyboardId.isPhoneKeyboard()) {
+            mState.setSymbolsKeyboard();
         }
     }
 
     public void saveKeyboardState() {
-        mSavedKeyboardState.save();
+        if (isKeyboardAvailable()) {
+            mState.onSaveKeyboardState();
+        }
     }
 
     public void onFinishInputView() {
@@ -229,532 +169,155 @@
         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));
+                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
+                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
+        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
+        // 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.
+        mKeyboardView.updateSpacebar(0.0f,
+                mSubtypeSwitcher.needsToDisplayLanguage(keyboard.mId.mLocale));
+        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
         final boolean localeChanged = (oldKeyboard == null)
                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
         mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
-        updateShiftState();
-    }
-
-    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();
+        final Keyboard keyboard = getKeyboard();
+        return keyboard != null && keyboard.mId.isAlphabetKeyboard();
     }
 
     public boolean isInputViewShown() {
         return mCurrentInputView != null && mCurrentInputView.isShown();
     }
 
+    public boolean isShiftedOrShiftLocked() {
+        final Keyboard keyboard = getKeyboard();
+        return keyboard != null && keyboard.isShiftedOrShiftLocked();
+    }
+
+    public boolean isManualTemporaryUpperCase() {
+        final Keyboard keyboard = getKeyboard();
+        return keyboard != null && keyboard.isManualTemporaryUpperCase();
+    }
+
     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() {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setShifted(int shiftMode) {
         mInputMethodService.mHandler.cancelUpdateShiftState();
-        if (DEBUG_STATE)
-            Log.d(TAG, "toggleShift:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        if (isAlphabetMode()) {
-            setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
-        } else {
-            toggleShiftInSymbol();
+        Keyboard keyboard = getKeyboard();
+        if (keyboard == null)
+            return;
+        switch (shiftMode) {
+        case AUTOMATIC_SHIFT:
+            keyboard.setAutomaticTemporaryUpperCase();
+            break;
+        case MANUAL_SHIFT:
+            keyboard.setShifted(true);
+            break;
+        case UNSHIFT:
+            keyboard.setShifted(false);
+            break;
         }
-    }
-
-    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();
     }
 
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setShiftLocked(boolean shiftLocked) {
+        mInputMethodService.mHandler.cancelUpdateShiftState();
+        Keyboard keyboard = getKeyboard();
+        if (keyboard == null)
+            return;
+        keyboard.setShiftLocked(shiftLocked);
+        mKeyboardView.invalidateAllKeys();
+        if (!shiftLocked) {
+            // 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();
+        }
+    }
+
     /**
      * 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 changeKeyboardMode() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "changeKeyboardMode:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        toggleKeyboardMode();
-        if (isShiftLocked() && isAlphabetMode())
-            setShiftLocked(true);
-        updateShiftState();
+    public void onPressKey(int code) {
+        mState.onPressKey(code);
     }
 
-    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());
     }
 
-    private void toggleShiftInSymbol() {
-        if (isAlphabetMode())
-            return;
-        final LatinKeyboard keyboard;
-        if (mCurrentId.equals(mSymbolsKeyboardId)
-                || !mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
-            keyboard = getKeyboard(mSymbolsShiftedKeyboardId);
-        } else {
-            keyboard = getKeyboard(mSymbolsKeyboardId);
-        }
-        setKeyboard(keyboard);
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsKeyboard() {
+        setKeyboard(mKeyboardSet.getSymbolsKeyboard());
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetKeyboard() {
+        setKeyboard(mKeyboardSet.getMainKeyboard());
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsShiftedKeyboard() {
+        setKeyboard(mKeyboardSet.getSymbolsShiftedKeyboard());
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void requestUpdatingShiftState() {
+        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
     }
 
     public boolean isInMomentarySwitchState() {
-        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
-                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+        return mState.isInMomentarySwitchState();
     }
 
     public 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() {
@@ -795,6 +358,9 @@
 
         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.
@@ -804,13 +370,14 @@
     }
 
     private void postSetInputView(final View newInputView) {
-        mInputMethodService.mHandler.post(new Runnable() {
+        final LatinIME latinIme = mInputMethodService;
+        latinIme.mHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (newInputView != null) {
-                    mInputMethodService.setInputView(newInputView);
+                    latinIme.setInputView(newInputView);
                 }
-                mInputMethodService.updateInputViewShown();
+                latinIme.updateInputViewShown();
             }
         });
     }
@@ -825,31 +392,18 @@
         }
     }
 
-    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);
-            }
+    public void onNetworkStateChanged() {
+        if (mKeyboardView != null) {
+            mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady());
         }
     }
 
-    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;
+    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
+        if (mIsAutoCorrectionActive != isAutoCorrection) {
+            mIsAutoCorrectionActive = isAutoCorrection;
+            if (mKeyboardView != null) {
+                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
+            }
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 04e6725..abc220e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -148,7 +148,7 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_SHOW_KEY_PREVIEW:
-                keyboardView.showKey(msg.arg1, tracker);
+                keyboardView.showKey(tracker);
                 break;
             case MSG_DISMISS_KEY_PREVIEW:
                 tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
@@ -156,16 +156,15 @@
             }
         }
 
-        public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
+        public void showKeyPreview(long delay, 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);
+                keyboardView.showKey(tracker);
             } else {
-                sendMessageDelayed(
-                        obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
+                sendMessageDelayed(obtainMessage(MSG_SHOW_KEY_PREVIEW, tracker), delay);
             }
         }
 
@@ -195,7 +194,7 @@
         }
     }
 
-    private static class KeyDrawParams {
+    /* package */ static class KeyDrawParams {
         // XML attributes
         public final int mKeyTextColor;
         public final int mKeyTextInactivatedColor;
@@ -284,7 +283,7 @@
         }
     }
 
-    protected static class KeyPreviewDrawParams {
+    /* package */ static class KeyPreviewDrawParams {
         // XML attributes.
         public final Drawable mPreviewBackground;
         public final Drawable mPreviewLeftBackground;
@@ -475,7 +474,6 @@
 
         if (mKeyboard == null) return;
 
-        final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
         final KeyDrawParams params = mKeyDrawParams;
         if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) {
             // Draw a single key.
@@ -483,8 +481,7 @@
                     + getPaddingLeft();
             final int keyDrawY = mInvalidatedKey.mY + getPaddingTop();
             canvas.translate(keyDrawX, keyDrawY);
-            onBufferDrawKey(mInvalidatedKey, mKeyboard, canvas, mPaint, params,
-                    isManualTemporaryUpperCase);
+            onDrawKey(mInvalidatedKey, canvas, mPaint, params);
             canvas.translate(-keyDrawX, -keyDrawY);
         } else {
             // Draw all keys.
@@ -492,7 +489,7 @@
                 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);
+                onDrawKey(key, canvas, mPaint, params);
                 canvas.translate(-keyDrawX, -keyDrawY);
             }
         }
@@ -515,38 +512,43 @@
         }
     }
 
-    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) {
+        if (key.isSpacer()) return;
+        onDrawKeyBackground(key, canvas, params);
+        onDrawKeyTopVisuals(key, canvas, paint, params);
+    }
 
-        // Draw key top visuals.
+    // Draw key background.
+    /* package */ 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.
+    /* package */ 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());
         }
 
@@ -555,7 +557,7 @@
         float positionX = centerX;
         if (key.mLabel != null) {
             // Switch the character to uppercase if shift is pressed
-            final CharSequence label = keyboard.adjustLabelCase(key.mLabel);
+            final CharSequence label = mKeyboard.adjustLabelCase(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,7 +600,7 @@
                         Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
             }
 
-            if (key.hasUppercaseLetter() && isManualTemporaryUpperCase) {
+            if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) {
                 paint.setColor(params.mKeyTextInactivatedColor);
             } else {
                 paint.setColor(params.mKeyTextColor);
@@ -628,7 +630,7 @@
                 }
             }
 
-            if (debugShowAlign) {
+            if (LatinImeLogger.sVISUALDEBUG) {
                 final Paint line = new Paint();
                 drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
                 drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line);
@@ -645,7 +647,7 @@
                 hintSize = params.mKeyHintLabelSize;
                 paint.setTypeface(Typeface.DEFAULT);
             } else if (key.hasUppercaseLetter()) {
-                hintColor = isManualTemporaryUpperCase
+                hintColor = mKeyboard.isManualTemporaryUpperCase()
                         ? params.mKeyUppercaseLetterActivatedColor
                         : params.mKeyUppercaseLetterInactivatedColor;
                 hintSize = params.mKeyUppercaseLetterSize;
@@ -678,7 +680,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,29 +705,35 @@
             }
             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 (key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0) {
+            drawKeyPopupHint(key, canvas, paint, params);
+        }
+    }
 
-            if (debugShowAlign) {
-                final Paint line = new Paint();
-                drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line);
-                drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line);
-            }
+    // Draw popup hint "..." at the bottom right corner of the key.
+    /* package */ 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);
+        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);
         }
     }
 
@@ -830,9 +838,9 @@
     }
 
     @Override
-    public void showKeyPreview(int keyIndex, PointerTracker tracker) {
+    public void showKeyPreview(PointerTracker tracker) {
         if (mShowKeyPreviewPopup) {
-            mDrawingHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
+            mDrawingHandler.showKeyPreview(mDelayBeforePreview, tracker);
         }
     }
 
@@ -858,7 +866,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 +875,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)
@@ -890,7 +898,7 @@
             }
             previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
         } else {
-            final Drawable previewIcon = key.getPreviewIcon();
+            final Drawable previewIcon = key.mPreviewIcon;
             previewText.setCompoundDrawables(null, null, null,
                    previewIcon != null ? previewIcon : key.getIcon());
             previewText.setText(null);
@@ -906,12 +914,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
@@ -932,6 +944,7 @@
     public void invalidateAllKeys() {
         mDirtyRect.union(0, 0, getWidth(), getHeight());
         mBufferNeedsUpdate = true;
+        mInvalidatedKey = null;
         invalidate();
     }
 
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..aa0f975 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -19,9 +19,18 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+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.os.Message;
-import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -39,10 +48,15 @@
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 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.Utils;
+import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
 import java.util.WeakHashMap;
 
 /**
@@ -58,11 +72,39 @@
 
     private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
 
+    /* Space key and its icons, drawables and colors. */
+    private Key mSpaceKey;
+    private Drawable mSpaceIcon;
+    private final boolean mIsSpacebarTriggeringPopupByLongPress;
+    private static final int SPACE_LED_LENGTH_PERCENT = 80;
+    private final boolean mAutoCorrectionSpacebarLedEnabled;
+    private final Drawable mAutoCorrectionSpacebarLedIcon;
+    private final float mSpacebarTextRatio;
+    private float mSpacebarTextSize;
+    private final int mSpacebarTextColor;
+    private final int mSpacebarTextShadowColor;
+    private final HashMap<Integer, BitmapDrawable> mSpacebarDrawableCache =
+            new HashMap<Integer, BitmapDrawable>();
+
+    private boolean mAutoCorrectionSpacebarLedOn;
+    private boolean mNeedsToDisplayLanguage;
+    private Locale mSpacebarLocale;
+    private float mSpacebarTextFadeFactor = 0.0f;
+
+    // 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 final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
 
     // Timing constants
     private final int mKeyRepeatInterval;
 
+    // TODO: Kill process when the usability study mode was changed.
+    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
+
     // Mini keyboard
     private PopupWindow mMoreKeysWindow;
     private MoreKeysPanel mMoreKeysPanel;
@@ -73,9 +115,9 @@
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
 
-    private final boolean mHasDistinctMultitouch;
+    private boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
-    private int mOldKeyIndex;
+    private Key mOldKey;
 
     private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
     protected KeyDetector mKeyDetector;
@@ -90,6 +132,7 @@
         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_KEY_TYPED = 4;
 
         private boolean mInKeyRepeat;
 
@@ -103,19 +146,19 @@
             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(keyboardView.mKeyRepeatInterval, tracker);
                 break;
             case MSG_LONGPRESS_KEY:
-                keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
+                keyboardView.openMiniKeyboardIfRequired(tracker.getKey(), tracker);
                 break;
             }
         }
 
         @Override
-        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
+        public void startKeyRepeatTimer(long delay, PointerTracker tracker) {
             mInKeyRepeat = true;
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
         }
 
         public void cancelKeyRepeatTimer() {
@@ -128,9 +171,9 @@
         }
 
         @Override
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+        public void startLongPressTimer(long delay, PointerTracker tracker) {
             cancelLongPressTimer();
-            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
         }
 
         @Override
@@ -139,6 +182,17 @@
         }
 
         @Override
+        public void startKeyTypedTimer(long delay) {
+            removeMessages(MSG_KEY_TYPED);
+            sendMessageDelayed(obtainMessage(MSG_KEY_TYPED), delay);
+        }
+
+        @Override
+        public boolean isTyping() {
+            return hasMessages(MSG_KEY_TYPED);
+        }
+
+        @Override
         public void cancelKeyTimers() {
             cancelKeyRepeatTimer();
             cancelLongPressTimer();
@@ -165,13 +219,13 @@
         @Override
         public boolean onDoubleTap(MotionEvent firstDown) {
             final Keyboard keyboard = getKeyboard();
-            if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard
-                    && ((LatinKeyboard) keyboard).isAlphaKeyboard()) {
+            if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard.mId.isAlphabetKeyboard()) {
                 final int pointerIndex = firstDown.getActionIndex();
                 final int id = firstDown.getPointerId(pointerIndex);
                 final PointerTracker tracker = getPointerTracker(id);
+                final Key key = tracker.getKeyOn((int)firstDown.getX(), (int)firstDown.getY());
                 // If the first down event is on shift key.
-                if (tracker.isOnShiftKey((int) firstDown.getX(), (int) firstDown.getY())) {
+                if (key != null && key.isShift()) {
                     mProcessingShiftDoubleTapEvent = true;
                     return true;
                 }
@@ -188,12 +242,13 @@
                 final int pointerIndex = secondDown.getActionIndex();
                 final int id = secondDown.getPointerId(pointerIndex);
                 final PointerTracker tracker = getPointerTracker(id);
+                final Key key = tracker.getKeyOn((int)secondDown.getX(), (int)secondDown.getY());
                 // If the second down event is also on shift key.
-                if (tracker.isOnShiftKey((int) secondDown.getX(), (int) secondDown.getY())) {
+                if (key != null && key.isShift()) {
                     // 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());
+                    onDoubleTapShiftKey(mKeyTimerHandler.isIgnoringDoubleTap());
                     return true;
                 }
                 // Otherwise these events should not be handled as double tap.
@@ -204,7 +259,7 @@
     }
 
     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) {
@@ -228,6 +283,23 @@
         mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
 
         PointerTracker.init(mHasDistinctMultitouch, getContext());
+
+        final int longPressSpaceKeyTimeout =
+                res.getInteger(R.integer.config_long_press_space_key_timeout);
+        mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0);
+
+        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);
+        a.recycle();
     }
 
     public void startIgnoringDoubleTap() {
@@ -264,20 +336,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 +354,13 @@
         PointerTracker.setKeyDetector(mKeyDetector);
         mTouchScreenRegulator.setKeyboard(keyboard);
         mMoreKeysPanelCache.clear();
+
+        mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE);
+        mSpaceIcon = keyboard.mIconsSet.getIconByAttrId(R.styleable.Keyboard_iconSpaceKey);
+        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
+        mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
+        mSpacebarLocale = keyboard.mId.mLocale;
+        clearSpacebarDrawableCache();
     }
 
     /**
@@ -306,6 +371,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 +398,7 @@
         super.cancelAllMessages();
     }
 
-    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+    private boolean openMiniKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
         // Check if we have a popup layout specified first.
         if (mMoreKeysLayout == 0) {
             return false;
@@ -338,20 +407,20 @@
         // 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) {
+    private void onDoubleTapShiftKey(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);
+        if (ignore) {
+            invokeCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
+        } else {
+            invokeCodeInput(Keyboard.CODE_CAPSLOCK);
+        }
     }
 
     // This default implementation returns a more keys panel.
@@ -374,15 +443,6 @@
         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);
-        }
-    }
-
     /**
      * Called when a key is long pressed. By default this will open mini keyboard associated
      * with this key.
@@ -394,48 +454,42 @@
     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 (primaryCode == Keyboard.CODE_DIGIT0 && keyboard.mId.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_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
-            // Both long pressing settings key and space key invoke IME switcher dialog.
+        if (primaryCode == Keyboard.CODE_SHIFT && keyboard.mId.isAlphabetKeyboard()) {
+            tracker.onLongPressed();
+            invokeCodeInput(Keyboard.CODE_CAPSLOCK);
+            invokeReleaseKey(primaryCode);
+            return true;
+        }
+        if (primaryCode == Keyboard.CODE_SPACE) {
+            // 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, null,
                 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) {
@@ -460,11 +514,10 @@
                 : 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;
     }
@@ -527,6 +580,33 @@
             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 + "\t\t");
+            }
+        }
 
         if (mKeyTimerHandler.isInKeyRepeat()) {
             final PointerTracker tracker = getPointerTracker(id);
@@ -548,8 +628,8 @@
                 // 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 +639,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);
@@ -583,6 +663,10 @@
                     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 + "\t\t");
+                }
             }
         } else {
             getPointerTracker(id).processMotionEvent(action, x, y, eventTime, this);
@@ -637,9 +721,8 @@
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
             return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
-                    event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
+                    event) || super.dispatchPopulateAccessibilityEvent(event);
         }
 
         return super.dispatchPopulateAccessibilityEvent(event);
@@ -663,4 +746,180 @@
         // 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);
+    }
+
+    public void updateSpacebar(float fadeFactor, boolean needsToDisplayLanguage) {
+        mSpacebarTextFadeFactor = fadeFactor;
+        mNeedsToDisplayLanguage = needsToDisplayLanguage;
+        updateSpacebarIcon();
+        invalidateKey(mSpaceKey);
+    }
+
+    public void updateAutoCorrectionState(boolean isAutoCorrection) {
+        if (!mAutoCorrectionSpacebarLedEnabled) return;
+        mAutoCorrectionSpacebarLedOn = isAutoCorrection;
+        updateSpacebarIcon();
+        invalidateKey(mSpaceKey);
+    }
+
+    @Override
+    /* package */ void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint,
+            KeyDrawParams params) {
+        super.onDrawKeyTopVisuals(key, canvas, paint, params);
+
+        if (key.mCode == Keyboard.CODE_SPACE) {
+            // Whether space key needs to show the "..." popup hint for special purposes
+            if (mIsSpacebarTriggeringPopupByLongPress
+                    && Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+                super.drawKeyPopupHint(key, canvas, paint, params);
+            }
+        }
+    }
+
+    // TODO: Get rid of this method and draw spacebar locale and auto correction spacebar LED
+    // in onDrawKeyTopVisuals.
+    private void updateSpacebarIcon() {
+        if (mSpaceKey == null) return;
+        if (mNeedsToDisplayLanguage) {
+            mSpaceKey.setIcon(getSpaceDrawable(mSpacebarLocale));
+        } else if (mAutoCorrectionSpacebarLedOn) {
+            mSpaceKey.setIcon(getSpaceDrawable(null));
+        } else {
+            mSpaceKey.setIcon(mSpaceIcon);
+        }
+    }
+
+    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;
+    }
+
+    // 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 locale language name 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 Integer getSpaceDrawableKey(Locale locale) {
+        return Arrays.hashCode(new Object[] {
+                locale,
+                mAutoCorrectionSpacebarLedOn,
+                mSpacebarTextFadeFactor
+        });
+    }
+
+    private void clearSpacebarDrawableCache() {
+        for (final BitmapDrawable drawable : mSpacebarDrawableCache.values()) {
+            final Bitmap bitmap = drawable.getBitmap();
+            bitmap.recycle();
+        }
+        mSpacebarDrawableCache.clear();
+    }
+
+    private BitmapDrawable getSpaceDrawable(Locale locale) {
+        final Integer hashCode = getSpaceDrawableKey(locale);
+        final BitmapDrawable cached = mSpacebarDrawableCache.get(hashCode);
+        if (cached != null) {
+            return cached;
+        }
+        final BitmapDrawable drawable = new BitmapDrawable(getResources(), drawSpacebar(
+                locale, mAutoCorrectionSpacebarLedOn, mSpacebarTextFadeFactor));
+        mSpacebarDrawableCache.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);
+        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 language = layoutSpacebar(paint, inputLocale, 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 = (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;
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index ac9290b..9742913 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -18,8 +18,6 @@
 
 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;
 
@@ -35,10 +33,10 @@
         return mDefaultKeyCoordX;
     }
 
-    public static class Builder extends KeyboardBuilder<Builder.MiniKeyboardParams> {
-        private final CharSequence[] mMoreKeys;
+    public static class Builder extends Keyboard.Builder<Builder.MiniKeyboardParams> {
+        private final String[] mMoreKeys;
 
-        public static class MiniKeyboardParams extends KeyboardParams {
+        public static class MiniKeyboardParams extends Keyboard.Params {
             /* package */int mTopRowAdjustment;
             public int mNumRows;
             public int mNumColumns;
@@ -207,7 +205,7 @@
 
         public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
             super(view.getContext(), new MiniKeyboardParams());
-            load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
+            load(xmlId, parentKeyboard.mId);
 
             // TODO: Mini keyboard's vertical gap is currently calculated heuristically.
             // Should revise the algorithm.
@@ -232,16 +230,14 @@
                     parentKey.mX + (mParams.mDefaultKeyWidth - width) / 2, view.getMeasuredWidth());
         }
 
-        private static int getMaxKeyWidth(KeyboardView view, CharSequence[] moreKeys,
-                int minKeyWidth) {
+        private static int getMaxKeyWidth(KeyboardView view, String[] 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.
+            for (String moreKeySpec : moreKeys) {
+                final String label = MoreKeySpecParser.getLabel(moreKeySpec);
+                // 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();
@@ -260,7 +256,7 @@
         public MiniKeyboard build() {
             final MiniKeyboardParams params = mParams;
             for (int n = 0; n < mMoreKeys.length; n++) {
-                final String moreKeySpec = mMoreKeys[n].toString();
+                final String moreKeySpec = mMoreKeys[n];
                 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);
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
index f2c5b7b..1f9ed5e 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
@@ -61,12 +61,13 @@
         }
 
         @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);
         }
     };
 
@@ -132,9 +133,8 @@
     @Override
     public void setShifted(boolean shifted) {
         final Keyboard keyboard = getKeyboard();
-        if (keyboard.setShifted(shifted)) {
-            invalidateAllKeys();
-        }
+        keyboard.setShifted(shifted);
+        invalidateAllKeys();
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index d202046..742ee98 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,23 @@
     }
 
     @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    public Key getKeyAndNearbyCodes(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 (allCodes != null && nearestKey != null) {
+            allCodes[0] = nearestKey.mCode;
+        }
+        return nearestKey;
     }
-}
\ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
index 6314a99..a3ff372 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
@@ -24,6 +24,7 @@
         public boolean dismissMoreKeysPanel();
     }
 
+    // TODO: Remove this method.
     public void setShifted(boolean shifted);
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 198e06a..274bd0b 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -18,6 +18,7 @@
 
 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;
@@ -27,8 +28,8 @@
 import com.android.inputmethod.latin.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 public class PointerTracker {
     private static final String TAG = PointerTracker.class.getSimpleName();
@@ -67,22 +68,28 @@
     public interface DrawingProxy extends MoreKeysPanel.Controller {
         public void invalidateKey(Key key);
         public TextView inflateKeyPreviewText();
-        public void showKeyPreview(int keyIndex, PointerTracker tracker);
+        public void showKeyPreview(PointerTracker tracker);
         public void cancelShowKeyPreview(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 startKeyTypedTimer(long delay);
+        public boolean isTyping();
+        public void startKeyRepeatTimer(long delay, PointerTracker tracker);
+        public void startLongPressTimer(long delay, PointerTracker tracker);
         public void cancelLongPressTimer();
         public void cancelKeyTimers();
 
         public static class Adapter implements TimerProxy {
             @Override
-            public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {}
+            public void startKeyTypedTimer(long delay) {}
             @Override
-            public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {}
+            public boolean isTyping() { return false; }
+            @Override
+            public void startKeyRepeatTimer(long delay, PointerTracker tracker) {}
+            @Override
+            public void startLongPressTimer(long delay, PointerTracker tracker) {}
             @Override
             public void cancelLongPressTimer() {}
             @Override
@@ -97,6 +104,7 @@
     private static int sLongPressKeyTimeout;
     private static int sLongPressShiftKeyTimeout;
     private static int sLongPressSpaceKeyTimeout;
+    private static int sIgnoreSpecialKeyTimeout;
     private static int sTouchNoiseThresholdMillis;
     private static int sTouchNoiseThresholdDistanceSquared;
 
@@ -111,7 +119,7 @@
     private KeyboardActionListener mListener = EMPTY_LISTENER;
 
     private Keyboard mKeyboard;
-    private List<Key> mKeys;
+    private Set<Key> mKeys;
     private int mKeyQuarterWidthSquared;
     private final TextView mKeyPreviewText;
 
@@ -119,9 +127,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;
 
@@ -167,7 +175,9 @@
         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);
+        sIgnoreSpecialKeyTimeout = res.getInteger(R.integer.config_ignore_special_key_timeout);
         sTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis);
+
         final float touchNoiseThresholdDistance = res.getDimension(
                 R.dimen.config_touch_noise_threshold_distance);
         sTouchNoiseThresholdDistanceSquared = (int)(
@@ -207,7 +217,7 @@
 
     public static void dismissAllKeyPreviews() {
         for (final PointerTracker tracker : sTrackers) {
-            tracker.setReleasedKeyGraphics(tracker.mKeyIndex);
+            tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
         }
     }
 
@@ -227,15 +237,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;
@@ -246,35 +259,45 @@
     // 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)
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+        final boolean alterCode = key.altCodeWhileTyping() && mTimerProxy.isTyping();
+        final int code = alterCode ? key.mAltCode : primaryCode;
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
+                    + " codes="+ KeyDetector.printableCodes(keyCodes) + " x=" + x + " y=" + y
+                    + " ignoreModifier=" + ignoreModifierKey + " alterCode=" + alterCode
+                    + " 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);
+        }
+        if (key.isEnabled()) {
+            if (code == Keyboard.CODE_OUTPUT_TEXT) {
+                mListener.onTextInput(key.mOutputText);
+            } else if (code != Keyboard.CODE_UNSPECIFIED) {
+                mListener.onCodeInput(code, keyCodes, x, y);
+            }
+            if (!key.altCodeWhileTyping() && !key.isModifier()) {
+                mTimerProxy.startKeyTypedTimer(sIgnoreSpecialKeyTimeout);
+            }
+        }
     }
 
     // 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() {
@@ -295,71 +318,69 @@
         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.getKeyAndNearbyCodes(x, y, null);
     }
 
-    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.isShift()) {
+                for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                    if (shiftKey != key) {
+                        shiftKey.onReleased();
+                        mDrawingProxy.invalidateKey(shiftKey);
+                    }
+                }
+            }
+
+            if (key.altCodeWhileTyping()) {
+                final Key altKey = mKeyboard.getKey(key.mAltCode);
+                if (altKey != null) {
+                    altKey.onReleased();
+                    mDrawingProxy.invalidateKey(altKey);
+                }
+            }
         }
     }
 
-    private void setPressedKeyGraphics(int keyIndex) {
-        final Key key = getKey(keyIndex);
+    private void setPressedKeyGraphics(Key key) {
         if (key != null && key.isEnabled()) {
-            if (isKeyPreviewRequired(key)) {
-                mDrawingProxy.showKeyPreview(keyIndex, this);
+            if (!key.noKeyPreview()) {
+                mDrawingProxy.showKeyPreview(this);
             }
             key.onPressed();
             mDrawingProxy.invalidateKey(key);
-        }
-    }
 
-    // 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;
+            if (key.isShift()) {
+                for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                    if (shiftKey != key) {
+                        shiftKey.onPressed();
+                        mDrawingProxy.invalidateKey(shiftKey);
+                    }
+                }
+            }
+
+            if (key.altCodeWhileTyping() && mTimerProxy.isTyping()) {
+                final Key altKey = mKeyboard.getKey(key.mAltCode);
+                if (altKey != null) {
+                    // TODO: Show altKey's preview.
+                    altKey.onPressed();
+                    mDrawingProxy.invalidateKey(altKey);
+                }
+            }
         }
-        return true;
     }
 
     public int getLastX() {
@@ -374,31 +395,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.getKeyAndNearbyCodes(x, y, null);
     }
 
-    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);
     }
 
@@ -447,7 +468,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 +480,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 = sConfigSlidingKeyInputEnabled
+                || (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 +520,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 +569,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 +596,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);
@@ -605,8 +631,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 +640,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);
@@ -648,7 +674,7 @@
     private void onCancelEventInternal() {
         mTimerProxy.cancelKeyTimers();
         mDrawingProxy.cancelShowKeyPreview(this);
-        setReleasedKeyGraphics(mKeyIndex);
+        setReleasedKeyGraphics(mCurrentKey);
         mIsInSlidingKeyInput = false;
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
@@ -656,48 +682,45 @@
         }
     }
 
-    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(sDelayBeforeKeyRepeatStart, 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) {
+    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
         if (mKeys == null || 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);
+    private void startLongPressTimer(Key key) {
         if (key == null) return;
         if (key.mCode == Keyboard.CODE_SHIFT) {
             if (sLongPressShiftKeyTimeout > 0) {
-                mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, keyIndex, this);
+                mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, this);
             }
         } else if (key.mCode == Keyboard.CODE_SPACE) {
             if (sLongPressSpaceKeyTimeout > 0) {
-                mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, keyIndex, this);
+                mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, this);
             }
         } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) {
             // We need not start long press timer on the key which has manual temporary upper case
@@ -705,59 +728,48 @@
             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);
+            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, this);
         } else {
-            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this);
+            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, 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;
-            }
+        int code = key.mCode;
+        final int[] codes = mKeyDetector.newCodeArray();
+        mKeyDetector.getKeyAndNearbyCodes(x, y, codes);
 
-            // 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);
+        // 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);
     }
 
     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.getKeyAndNearbyCodes(x, y, null);
+        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..c1dae06 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -18,19 +18,19 @@
 
 import android.graphics.Rect;
 
-import com.android.inputmethod.keyboard.internal.KeyboardParams.TouchPositionCorrection;
+import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
+import java.util.Set;
 
 public class ProximityInfo {
     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
     /** Number of key widths from current touch point to search for nearest keys. */
     private static float SEARCH_DISTANCE = 1.2f;
-    private static final int[] EMPTY_INT_ARRAY = new int[0];
+    private static final Key[] EMPTY_KEY_ARRAY = new Key[0];
 
     private final int mKeyHeight;
     private final int mGridWidth;
@@ -41,10 +41,10 @@
     // TODO: Find a proper name for mKeyboardMinWidth
     private final int mKeyboardMinWidth;
     private final int mKeyboardHeight;
-    private final int[][] mGridNeighbors;
+    private final Key[][] mGridNeighbors;
 
     ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth,
-            int keyHeight, List<Key> keys, TouchPositionCorrection touchPositionCorrection) {
+            int keyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection) {
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -53,7 +53,7 @@
         mKeyboardMinWidth = minWidth;
         mKeyboardHeight = height;
         mKeyHeight = keyHeight;
-        mGridNeighbors = new int[mGridSize][];
+        mGridNeighbors = new Key[mGridSize][];
         if (minWidth == 0 || height == 0) {
             // No proximity required. Keyboard might be mini keyboard.
             return;
@@ -62,40 +62,40 @@
     }
 
     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, Collections.<Key>emptySet(), null);
     }
 
-    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,
+                        480, 300, 11, 3, proximity,
                         0, null, null, null, null, null, null, null, null);
         return spellCheckerProximityInfo;
     }
 
-    private int mNativeProximityInfo;
+    private long mNativeProximityInfo;
     static {
         Utils.loadNativeLibrary();
     }
-    private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
+    private native long setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
             int displayHeight, int gridWidth, int gridHeight, 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 native void releaseProximityInfoNative(long nativeProximityInfo);
 
-    private final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth,
-            int keyboardHeight, List<Key> keys,
+    private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth,
+            int keyboardHeight, Set<Key> keys,
             TouchPositionCorrection touchPositionCorrection) {
-        int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
+        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();
@@ -104,25 +104,45 @@
         final int[] keyWidths = new int[keyCount];
         final int[] keyHeights = new int[keyCount];
         final int[] keyCharCodes = new int[keyCount];
-        for (int i = 0; i < keyCount; ++i) {
-            final Key key = keys.get(i);
+        final float[] sweetSpotCenterXs;
+        final float[] sweetSpotCenterYs;
+        final float[] sweetSpotRadii;
+        final boolean calculateSweetSpotParams;
+        if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
+            sweetSpotCenterXs = new float[keyCount];
+            sweetSpotCenterYs = new float[keyCount];
+            sweetSpotRadii = new float[keyCount];
+            calculateSweetSpotParams = true;
+        } else {
+            sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
+            calculateSweetSpotParams = false;
+        }
+
+        int i = 0;
+        for (final Key key : keys) {
             keyXCoordinates[i] = key.mX;
             keyYCoordinates[i] = key.mY;
             keyWidths[i] = key.mWidth;
             keyHeights[i] = key.mHeight;
             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);
+            if (calculateSweetSpotParams) {
+                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);
+                }
+            }
+            i++;
         }
 
         mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
@@ -131,33 +151,7 @@
                 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 +167,12 @@
         }
     }
 
-    private void computeNearestNeighbors(int defaultWidth, List<Key> keys,
+    private void computeNearestNeighbors(int defaultWidth, Set<Key> keys,
             TouchPositionCorrection touchPositionCorrection) {
         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.size()];
         final int gridWidth = mGridWidth * mCellWidth;
         final int gridHeight = mGridHeight * mCellHeight;
         for (int x = 0; x < gridWidth; x += mCellWidth) {
@@ -186,24 +180,23 @@
                 final int centerX = x + mCellWidth / 2;
                 final int centerY = y + mCellHeight / 2;
                 int count = 0;
-                for (int i = 0; i < keys.size(); i++) {
-                    final Key key = keys.get(i);
+                for (final Key key : keys) {
                     if (key.isSpacer()) continue;
-                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold)
-                        indices[count++] = i;
+                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
+                        neighborKeys[count++] = key;
+                    }
                 }
-                final int[] cell = new int[count];
-                System.arraycopy(indices, 0, cell, 0, count);
-                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] = cell;
+                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
+                        Arrays.copyOfRange(neighborKeys, 0, count);
             }
         }
         setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys,
                 touchPositionCorrection);
     }
 
-    public int[] getNearestKeys(int x, int y) {
+    public 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);
@@ -211,6 +204,6 @@
                 return mGridNeighbors[index];
             }
         }
-        return EMPTY_INT_ARRAY;
+        return EMPTY_KEY_ARRAY;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index b385b7a..faea389 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -16,19 +16,23 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.content.res.Resources;
 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.Utils;
+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,20 +40,19 @@
     private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle();
 
     public interface KeyStyle {
-        public CharSequence[] getTextArray(TypedArray a, int index);
+        public String[] getTextArray(TypedArray a, int index);
         public CharSequence getText(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);
     }
 
-    /* package */ static class EmptyKeyStyle implements KeyStyle {
-        private EmptyKeyStyle() {
+    private static class EmptyKeyStyle implements KeyStyle {
+        EmptyKeyStyle() {
             // Nothing to do.
         }
 
         @Override
-        public CharSequence[] getTextArray(TypedArray a, int index) {
+        public String[] getTextArray(TypedArray a, int index) {
             return parseTextArray(a, index);
         }
 
@@ -68,59 +71,67 @@
             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) {
+        protected static String[] parseTextArray(TypedArray a, int index) {
             if (!a.hasValue(index))
                 return null;
             final CharSequence text = a.getText(index);
-            return parseCsvText(text);
+            return parseCsvText(text.toString(), a.getResources(), R.string.english_ime_name);
+        }
+    }
+
+    /* package for test */
+    static String[] parseCsvText(String rawText, Resources res, int packageNameResId) {
+        final String text = Utils.resolveStringResource(rawText, res, packageNameResId);
+        final int size = text.length();
+        if (size == 0) {
+            return null;
+        }
+        if (size == 1) {
+            return new String[] { 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);
+        final StringBuilder sb = new StringBuilder();
+        ArrayList<String> 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<String>();
                 }
+                if (sb.length() == 0) {
+                    list.add(text.substring(start, pos));
+                } else {
+                    list.add(sb.toString());
+                    sb.setLength(0);
+                }
+                start = pos + 1;
+                continue;
+            } else if (c == Utils.ESCAPE_CHAR) {
+                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()]);
-            }
+        }
+        if (list == null) {
+            return new String[] {
+                    sb.length() > 0 ? sb.toString() : text.substring(start)
+            };
+        } else {
+            list.add(sb.length() > 0 ? sb.toString() : text.substring(start));
+            return list.toArray(new String[list.size()]);
         }
     }
 
@@ -128,9 +139,9 @@
         private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>();
 
         @Override
-        public CharSequence[] getTextArray(TypedArray a, int index) {
+        public String[] getTextArray(TypedArray a, int index) {
             return a.hasValue(index)
-                    ? super.getTextArray(a, index) : (CharSequence[])mAttributes.get(index);
+                    ? super.getTextArray(a, index) : (String[])mAttributes.get(index);
         }
 
         @Override
@@ -151,31 +162,25 @@
             return super.getFlag(a, index, defaultValue) | (value != null ? value : 0);
         }
 
-        @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() {
+        DeclaredKeyStyle() {
             super();
         }
 
-        private void parseKeyStyleAttributes(TypedArray keyAttr) {
+        void parseKeyStyleAttributes(TypedArray keyAttr) {
             // TODO: Currently not all Key attributes can be declared as style.
             readInt(keyAttr, R.styleable.Keyboard_Key_code);
+            readInt(keyAttr, R.styleable.Keyboard_Key_altCode);
             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);
+            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) {
@@ -194,29 +199,25 @@
                 mAttributes.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 readTextArray(TypedArray a, int index) {
             final CharSequence[] value = parseTextArray(a, index);
             if (value != null)
                 mAttributes.put(index, value);
         }
 
-        private void addParent(DeclaredKeyStyle parentStyle) {
+        void addParent(DeclaredKeyStyle parentStyle) {
             mAttributes.putAll(parentStyle.mAttributes);
         }
     }
 
     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));
+                Keyboard.Builder.TAG_KEY_STYLE, styleName));
         if (mStyles.containsKey(styleName))
-            throw new ParseException("duplicate key style declared: " + styleName, parser);
+            throw new XmlParseUtils.ParseException(
+                    "duplicate key style declared: " + styleName, parser);
 
         final DeclaredKeyStyle style = new DeclaredKeyStyle();
         if (keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) {
@@ -224,7 +225,8 @@
                     R.styleable.Keyboard_KeyStyle_parentStyle);
             final DeclaredKeyStyle parent = mStyles.get(parentStyle);
             if (parent == null)
-                throw new ParseException("Unknown parentStyle " + parentStyle, parser);
+                throw new XmlParseUtils.ParseException(
+                        "Unknown parentStyle " + parentStyle, parser);
             style.addParent(parent);
         }
         style.parseKeyStyleAttributes(keyAttrs);
@@ -235,7 +237,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..09ecbca 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -23,80 +23,88 @@
 
 import com.android.inputmethod.latin.R;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
 public class KeyboardIconsSet {
     private static final String TAG = KeyboardIconsSet.class.getSimpleName();
 
     public static final int ICON_UNDEFINED = 0;
+    private static final int ATTR_UNDEFINED = 0;
 
-    // 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 Map<Integer, Drawable> mIcons = new HashMap<Integer, Drawable>();
 
-    private static final int ICON_LAST = 11;
+    // The key value should be aligned with the enum value of Keyboard.icon*.
+    private static final Map<Integer, Integer> ICONS_TO_ATTRS_MAP = new HashMap<Integer, Integer>();
+    private static final Map<String, Integer> NAME_TO_ATTRS_MAP = new HashMap<String, Integer>();
+    private static final Collection<Integer> VALID_ATTRS;
 
-    private final Drawable mIcons[] = new Drawable[ICON_LAST + 1];
+    static {
+        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);
+        VALID_ATTRS = ICONS_TO_ATTRS_MAP.values();
+    }
 
-    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, Integer attrId) {
+        ICONS_TO_ATTRS_MAP.put(iconId, attrId);
+        NAME_TO_ATTRS_MAP.put(name, attrId);
     }
 
     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 : VALID_ATTRS) {
+            try {
+                final Drawable icon = keyboardAttrs.getDrawable(attrId);
+                if (icon == null) continue;
+                setDefaultBounds(icon);
+                mIcons.put(attrId, 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)
+    public static int getIconAttrId(final Integer iconId) {
+        if (iconId == ICON_UNDEFINED) {
+            return ATTR_UNDEFINED;
+        }
+        final Integer attrId = ICONS_TO_ATTRS_MAP.get(iconId);
+        if (attrId == null) {
             throw new IllegalArgumentException("icon id is out of range: " + iconId);
-        return mIcons[iconId];
+        }
+        return attrId;
+    }
+
+    public static int getIconAttrId(final String iconName) {
+        final Integer attrId = NAME_TO_ATTRS_MAP.get(iconName);
+        if (attrId == null) {
+            throw new IllegalArgumentException("unknown icon name: " + iconName);
+        }
+        return attrId;
+    }
+
+    public Drawable getIconByAttrId(final Integer attrId) {
+        if (attrId == ATTR_UNDEFINED) {
+            return null;
+        }
+        if (!VALID_ATTRS.contains(attrId)) {
+            throw new IllegalArgumentException("unknown icon attribute id: " + attrId);
+        }
+        return mIcons.get(attrId);
     }
 
     private static Drawable setDefaultBounds(final Drawable 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/KeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
index 28a53ce..4608e22 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
@@ -18,11 +18,9 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-
 public class KeyboardShiftState {
     private static final String TAG = KeyboardShiftState.class.getSimpleName();
-    private static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
+    private static final boolean DEBUG = false;
 
     private static final int NORMAL = 0;
     private static final int MANUAL_SHIFTED = 1;
@@ -33,7 +31,7 @@
 
     private int mState = NORMAL;
 
-    public boolean setShifted(boolean newShiftState) {
+    public void setShifted(boolean newShiftState) {
         final int oldState = mState;
         if (newShiftState) {
             switch (oldState) {
@@ -61,7 +59,6 @@
         }
         if (DEBUG)
             Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this);
-        return mState != oldState;
     }
 
     public void setShiftLocked(boolean newShiftLockState) {
@@ -76,12 +73,7 @@
                 break;
             }
         } else {
-            switch (oldState) {
-            case SHIFT_LOCKED:
-            case SHIFT_LOCK_SHIFTED:
-                mState = NORMAL;
-                break;
-            }
+            mState = NORMAL;
         }
         if (DEBUG)
             Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState)
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..babf600
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -0,0 +1,535 @@
+/*
+ * 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)}.
+ *
+ * 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 static final int UNSHIFT = 0;
+        public static final int MANUAL_SHIFT = 1;
+        public static final int AUTOMATIC_SHIFT = 2;
+        public void setShifted(int shiftMode);
+
+        public void setShiftLocked(boolean shiftLocked);
+
+        public void setSymbolsKeyboard();
+
+        public void setSymbolsShiftedKeyboard();
+
+        /**
+         * Request to call back {@link KeyboardState#onUpdateShiftState(boolean)}.
+         */
+        public void requestUpdatingShiftState();
+    }
+
+    private final SwitchActions mSwitchActions;
+
+    private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
+    private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
+
+    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 String mLayoutSwitchBackSymbols;
+
+    private boolean mIsAlphabetMode;
+    private KeyboardShiftState mAlphabetShiftState = new KeyboardShiftState();
+    private boolean mIsSymbolShifted;
+    private boolean mPrevMainKeyboardWasShiftLocked;
+    private boolean mPrevSymbolsKeyboardWasShifted;
+
+    private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
+
+    static class SavedKeyboardState {
+        public boolean mIsValid;
+        public boolean mIsAlphabetMode;
+        public boolean mIsShiftLocked;
+        public boolean mIsShifted;
+
+        public String toString() {
+            if (!mIsValid) return "INVALID";
+            if (mIsAlphabetMode) {
+                if (mIsShiftLocked) 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.mIsShiftLocked = mAlphabetShiftState.isShiftLocked();
+            state.mIsShifted = !state.mIsShiftLocked
+                    && mAlphabetShiftState.isShiftedOrShiftLocked();
+        } else {
+            state.mIsShiftLocked = false;
+            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.mIsShiftLocked);
+            if (!state.mIsShiftLocked) {
+                setShifted(state.mIsShifted ? SwitchActions.MANUAL_SHIFT : SwitchActions.UNSHIFT);
+            }
+        }
+    }
+
+    // TODO: Remove this method.
+    public boolean isShiftLocked() {
+        return mAlphabetShiftState.isShiftLocked();
+    }
+
+    private void setShifted(int shiftMode) {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode));
+        }
+        switch (shiftMode) {
+        case SwitchActions.AUTOMATIC_SHIFT:
+            mAlphabetShiftState.setAutomaticTemporaryUpperCase();
+            break;
+        case SwitchActions.MANUAL_SHIFT:
+            mAlphabetShiftState.setShifted(true);
+            break;
+        case SwitchActions.UNSHIFT:
+            mAlphabetShiftState.setShifted(false);
+            break;
+        }
+        mSwitchActions.setShifted(shiftMode);
+    }
+
+    private void setShiftLocked(boolean shiftLocked) {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked);
+        }
+        mAlphabetShiftState.setShiftLocked(shiftLocked);
+        mSwitchActions.setShiftLocked(shiftLocked);
+    }
+
+    private void toggleAlphabetAndSymbols() {
+        if (mIsAlphabetMode) {
+            setSymbolsKeyboard();
+        } else {
+            setAlphabetKeyboard();
+        }
+    }
+
+    private void toggleShiftInSymbols() {
+        if (mIsSymbolShifted) {
+            setSymbolsKeyboard();
+        } else {
+            setSymbolsShiftedKeyboard();
+        }
+    }
+
+    private void setAlphabetKeyboard() {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setAlphabetKeyboard");
+        }
+        mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
+        mSwitchActions.setAlphabetKeyboard();
+        mIsAlphabetMode = true;
+        mIsSymbolShifted = false;
+        mSwitchState = SWITCH_STATE_ALPHA;
+        setShiftLocked(mPrevMainKeyboardWasShiftLocked);
+        mPrevMainKeyboardWasShiftLocked = false;
+        mSwitchActions.requestUpdatingShiftState();
+    }
+
+    // TODO: Make this method private
+    public void setSymbolsKeyboard() {
+        mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
+        if (mPrevSymbolsKeyboardWasShifted) {
+            setSymbolsShiftedKeyboard();
+            return;
+        }
+
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setSymbolsKeyboard");
+        }
+        mSwitchActions.setSymbolsKeyboard();
+        mIsAlphabetMode = false;
+        mIsSymbolShifted = false;
+        // Reset alphabet shift state.
+        mAlphabetShiftState.setShiftLocked(false);
+        mPrevSymbolsKeyboardWasShifted = 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);
+        mPrevSymbolsKeyboardWasShifted = 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 {
+            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) {
+            // TODO: Make use of withSliding instead of relying on mSwitchState.
+            onReleaseSymbol();
+        }
+    }
+
+    private void onPressSymbol() {
+        toggleAlphabetAndSymbols();
+        mSymbolKeyState.onPress();
+        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
+    }
+
+    private void onReleaseSymbol() {
+        // Switch 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) {
+            toggleAlphabetAndSymbols();
+        }
+        mSymbolKeyState.onRelease();
+    }
+
+    public void onUpdateShiftState(boolean autoCaps) {
+        if (DEBUG_EVENT) {
+            Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
+        }
+        onUpdateShiftStateInternal(autoCaps);
+    }
+
+    private void onUpdateShiftStateInternal(boolean autoCaps) {
+        if (mIsAlphabetMode) {
+            if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) {
+                if (mShiftKeyState.isReleasing() && autoCaps) {
+                    // Only when shift key is releasing, automatic temporary upper case will be set.
+                    setShifted(SwitchActions.AUTOMATIC_SHIFT);
+                } else {
+                    setShifted(mShiftKeyState.isMomentary()
+                            ? SwitchActions.MANUAL_SHIFT : SwitchActions.UNSHIFT);
+                }
+            }
+        } else {
+            // In symbol keyboard mode, we should clear shift key state because only alphabet
+            // keyboard has shift key.
+            mSymbolKeyState.onRelease();
+        }
+    }
+
+    private void onPressShift() {
+        if (mIsAlphabetMode) {
+            if (mAlphabetShiftState.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.
+                setShifted(SwitchActions.MANUAL_SHIFT);
+                mShiftKeyState.onPress();
+            } else if (mAlphabetShiftState.isAutomaticTemporaryUpperCase()) {
+                // Shift key is pressed while automatic temporary upper case, we have to move to
+                // manual temporary upper case.
+                setShifted(SwitchActions.MANUAL_SHIFT);
+                mShiftKeyState.onPress();
+            } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
+                // In manual upper case state, we just record shift key has been pressing while
+                // shifted state.
+                mShiftKeyState.onPressOnShifted();
+            } else {
+                // In base layout, chording or manual temporary upper case mode is started.
+                setShifted(SwitchActions.MANUAL_SHIFT);
+                mShiftKeyState.onPress();
+            }
+        } 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();
+            if (mShiftKeyState.isMomentary()) {
+                // After chording input while normal state.
+                if (mAlphabetShiftState.isShiftLockShifted()) {
+                    setShiftLocked(true);
+                } else {
+                    setShifted(SwitchActions.UNSHIFT);
+                }
+            } 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 caps lock state.
+                setShiftLocked(false);
+            } else if (mAlphabetShiftState.isShiftedOrShiftLocked()
+                    && mShiftKeyState.isPressingOnShifted() && !withSliding) {
+                // Shift has been pressed without chording while shifted state.
+                setShifted(SwitchActions.UNSHIFT);
+            } else if (mAlphabetShiftState.isManualTemporaryUpperCaseFromAuto()
+                    && mShiftKeyState.isPressing() && !withSliding) {
+                // Shift has been pressed without chording while manual temporary upper case
+                // transited from automatic temporary upper case.
+                setShifted(SwitchActions.UNSHIFT);
+            }
+        } 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 (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
+                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);
+        }
+
+        if (mIsAlphabetMode && code == Keyboard.CODE_CAPSLOCK) {
+            if (mAlphabetShiftState.isShiftLocked()) {
+                setShiftLocked(false);
+                // Shift key is long pressed or double tapped while caps lock state, we will
+                // toggle back to normal state. And mark as if shift key is released.
+                mShiftKeyState.onRelease();
+            } else {
+                setShiftLocked(true);
+            }
+        }
+
+        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 (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();
+            } else {
+                // Chording input is being started. The keyboard mode will be switched 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 (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;
+            } else {
+                // Chording input is being started. The keyboard mode will be switched 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) && (Keyboard.isLetterCode(code)
+                    || code == Keyboard.CODE_OUTPUT_TEXT)) {
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            }
+            // Switch back to alpha keyboard mode immediately if user types a quote character.
+            if (isLayoutSwitchBackCharacter(code)) {
+                setAlphabetKeyboard();
+            }
+            break;
+        case SWITCH_STATE_SYMBOL:
+        case SWITCH_STATE_CHORDING_SYMBOL:
+            // Switch 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)) {
+                setAlphabetKeyboard();
+            }
+            break;
+        }
+
+        // If the code is a letter, update keyboard shift state.
+        if (Keyboard.isLetterCode(code)) {
+            onUpdateShiftStateInternal(autoCaps);
+        }
+    }
+
+    private static String shiftModeToString(int shiftMode) {
+        switch (shiftMode) {
+        case SwitchActions.UNSHIFT: return "UNSHIFT";
+        case SwitchActions.MANUAL_SHIFT: return "MANUAL";
+        case SwitchActions.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";
+        case SWITCH_STATE_CHORDING_ALPHA: return "CHORDING-ALPHA";
+        case SWITCH_STATE_CHORDING_SYMBOL: return "CHORDING-SYMBOL";
+        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..f95c916 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
@@ -18,11 +18,9 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-
-public class ModifierKeyState {
+/* package */ class ModifierKeyState {
     protected static final String TAG = "ModifierKeyState";
-    protected static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
+    protected static final boolean DEBUG = false;
 
     protected static final int RELEASING = 0;
     protected static final int PRESSING = 1;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java
index a490b0a..1677773 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParser.java
@@ -18,10 +18,10 @@
 
 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 com.android.inputmethod.latin.Utils;
 
 import java.util.ArrayList;
 
@@ -31,20 +31,16 @@
  * 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)
+ * - 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_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 static final char LABEL_END = '|';
+    private static final String PREFIX_ICON = Utils.PREFIX_AT + "icon" + Utils.SUFFIX_SLASH;
+    private static final String PREFIX_CODE = Utils.PREFIX_AT + "integer" + Utils.SUFFIX_SLASH;
 
     private MoreKeySpecParser() {
         // Intentional empty constructor for utility class.
@@ -53,8 +49,9 @@
     private static boolean hasIcon(String moreKeySpec) {
         if (moreKeySpec.startsWith(PREFIX_ICON)) {
             final int end = indexOfLabelEnd(moreKeySpec, 0);
-            if (end > 0)
+            if (end > 0) {
                 return true;
+            }
             throw new MoreKeySpecParserError("outputText or code not specified: " + moreKeySpec);
         }
         return false;
@@ -70,13 +67,14 @@
     }
 
     private static String parseEscape(String text) {
-        if (text.indexOf(ESCAPE) < 0)
+        if (text.indexOf(Utils.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 && pos + 1 < length) {
+            if (c == Utils.ESCAPE_CHAR && pos + 1 < length) {
                 sb.append(text.charAt(++pos));
             } else {
                 sb.append(c);
@@ -86,18 +84,19 @@
     }
 
     private static int indexOfLabelEnd(String moreKeySpec, int start) {
-        if (moreKeySpec.indexOf(ESCAPE, start) < 0) {
+        if (moreKeySpec.indexOf(Utils.ESCAPE_CHAR, start) < 0) {
             final int end = moreKeySpec.indexOf(LABEL_END, start);
-            if (end == 0)
+            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) {
+            if (c == Utils.ESCAPE_CHAR && pos + 1 < length) {
                 pos++;
-            } else if (moreKeySpec.startsWith(LABEL_END, pos)) {
+            } else if (c == LABEL_END) {
                 return pos;
             }
         }
@@ -105,79 +104,75 @@
     }
 
     public static String getLabel(String moreKeySpec) {
-        if (hasIcon(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))
+        if (TextUtils.isEmpty(label)) {
             throw new MoreKeySpecParserError("Empty label: " + moreKeySpec);
+        }
         return label;
     }
 
     public static String getOutputText(String moreKeySpec) {
-        if (hasCode(moreKeySpec))
+        if (hasCode(moreKeySpec)) {
             return null;
+        }
         final int end = indexOfLabelEnd(moreKeySpec, 0);
         if (end > 0) {
-            if (indexOfLabelEnd(moreKeySpec, end + 1) >= 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))
+            }
+            final String outputText = parseEscape(
+                    moreKeySpec.substring(end + /* LABEL_END */1));
+            if (!TextUtils.isEmpty(outputText)) {
                 return outputText;
+            }
             throw new MoreKeySpecParserError("Empty outputText: " + moreKeySpec);
         }
         final String label = getLabel(moreKeySpec);
-        if (label == null)
+        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;
+        return (label.length() == 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)
+            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 resId = Utils.getResourceId(res,
+                    moreKeySpec.substring(end + /* LABEL_END */1 + /* PREFIX_AT */1),
+                    R.string.english_ime_name);
             final int code = res.getInteger(resId);
             return code;
         }
-        if (indexOfLabelEnd(moreKeySpec, 0) > 0)
-            return Keyboard.CODE_DUMMY;
+        if (indexOfLabelEnd(moreKeySpec, 0) > 0) {
+            return Keyboard.CODE_OUTPUT_TEXT;
+        }
         final String label = getLabel(moreKeySpec);
         // Code is automatically generated for one letter label.
-        if (label != null && label.length() == 1)
+        if (label != null && label.length() == 1) {
             return label.charAt(0);
-        return Keyboard.CODE_DUMMY;
+        }
+        return Keyboard.CODE_OUTPUT_TEXT;
     }
 
-    public static int getIconId(String moreKeySpec) {
+    public static int getIconAttrId(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;
-            }
+            final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
+            final String name = moreKeySpec.substring(PREFIX_ICON.length(), end);
+            return KeyboardIconsSet.getIconAttrId(name);
         }
         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) {
@@ -196,21 +191,19 @@
         }
     };
 
-    public static CharSequence[] filterOut(Resources res, CharSequence[] moreKeys,
-            CodeFilter filter) {
+    public static String[] filterOut(Resources res, String[] moreKeys, CodeFilter filter) {
         if (moreKeys == null || moreKeys.length < 1) {
             return null;
         }
-        if (moreKeys.length == 1
-                && filter.shouldFilterOut(getCode(res, moreKeys[0].toString()))) {
+        if (moreKeys.length == 1 && filter.shouldFilterOut(getCode(res, moreKeys[0]))) {
             return null;
         }
-        ArrayList<CharSequence> filtered = null;
+        ArrayList<String> filtered = null;
         for (int i = 0; i < moreKeys.length; i++) {
-            final CharSequence moreKeySpec = moreKeys[i];
-            if (filter.shouldFilterOut(getCode(res, moreKeySpec.toString()))) {
+            final String moreKeySpec = moreKeys[i];
+            if (filter.shouldFilterOut(getCode(res, moreKeySpec))) {
                 if (filtered == null) {
-                    filtered = new ArrayList<CharSequence>();
+                    filtered = new ArrayList<String>();
                     for (int j = 0; j < i; j++) {
                         filtered.add(moreKeys[j]);
                     }
@@ -225,6 +218,6 @@
         if (filtered.size() == 0) {
             return null;
         }
-        return filtered.toArray(new CharSequence[filtered.size()]);
+        return filtered.toArray(new String[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..d9181f7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,6 +18,7 @@
 
 import com.android.inputmethod.keyboard.PointerTracker;
 
+import java.util.Iterator;
 import java.util.LinkedList;
 
 public class PointerTrackerQueue {
@@ -27,18 +28,23 @@
         mQueue.add(tracker);
     }
 
+    public synchronized void remove(PointerTracker tracker) {
+        mQueue.remove(tracker);
+    }
+
     public synchronized void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) {
-        if (mQueue.lastIndexOf(tracker) < 0) {
+        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,20 +54,14 @@
     }
 
     public synchronized void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
-        for (PointerTracker t : mQueue) {
-            if (t == tracker) {
-                continue;
+        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();
             }
-            t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
         }
-        mQueue.clear();
-        if (tracker != null) {
-            mQueue.add(tracker);
-        }
-    }
-
-    public synchronized void remove(PointerTracker tracker) {
-        mQueue.remove(tracker);
     }
 
     public synchronized boolean isAnyInSlidingKeyInput() {
@@ -75,13 +75,12 @@
 
     @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);
         }
-        sb.append("]");
-        return sb.toString();
+        return "[" + sb + "]";
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
index 6617b91..6f54b4f 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;
 
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 485ec51..bcb7891 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -74,6 +74,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,7 +106,7 @@
         return whiteListedWord != null;
     }
 
-    private boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
+    private static boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
             WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
             int correctionMode) {
         if (TextUtils.isEmpty(typedWord)) return false;
@@ -118,8 +126,9 @@
             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);
+            mNormalizedScore = BinaryDictionary.calcNormalizedScore(
+                    typedWord.toString(), autoCorrectionSuggestion.toString(),
+                    autoCorrectionSuggestionScore);
             if (DBG) {
                 Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionSuggestion + ","
                         + autoCorrectionSuggestionScore + ", " + mNormalizedScore
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b9fd574..3692ac1 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];
@@ -107,17 +107,21 @@
         Utils.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,
@@ -211,6 +215,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/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2..3805da1 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -30,6 +30,7 @@
 
     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 +61,8 @@
                 updateDebugMode();
                 mServiceNeedsRestart = true;
             }
+        } else if (key.equals(FORCE_NON_DISTINCT_MULTITOUCH_KEY)) {
+            mServiceNeedsRestart = true;
         }
     }
 
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/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..7eec8e2 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;
@@ -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..f5cf953
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -0,0 +1,117 @@
+/*
+ * 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 mInsertSpaceOnPickSuggestionManually;
+    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));
+            }
+            mInsertSpaceOnPickSuggestionManually = false;
+            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 (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 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;
+        }
+    }
+
+    // Pretty print
+    @Override
+    public String toString() {
+        return "\n mInsertSpaceOnPickSuggestionManually = " + mInsertSpaceOnPickSuggestionManually
+                + "\n mInputTypeNoAutoCorrect = " + mInputTypeNoAutoCorrect
+                + "\n mIsSettingsSuggestionStripOn = " + mIsSettingsSuggestionStripOn
+                + "\n mApplicationSpecifiedCompletionOn = " + mApplicationSpecifiedCompletionOn;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9c321bc..94c47bd 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -62,10 +62,11 @@
 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;
@@ -77,7 +78,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;
 
@@ -104,10 +104,14 @@
      */
     public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey";
 
+    // TODO: Remove this private option.
     /**
      * 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 +147,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,7 +162,24 @@
         SUGGESTION_VISIBILILTY_HIDE_VALUE
     };
 
-    private Settings.Values mSettingsValues;
+    // 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.
+    // Weak space: a space that should be swapped only by suggestion strip punctuation.
+    // 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.
+    // Swap punctuation: the state where a (weak or magic) space and a punctuation from the
+    // suggestion strip have just been swapped. Undoing this swaps them back.
+    private static final int SPACE_STATE_NONE = 0;
+    private static final int SPACE_STATE_DOUBLE = 1;
+    private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
+    private static final int SPACE_STATE_MAGIC = 3;
+    private static final int SPACE_STATE_WEAK = 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;
@@ -169,7 +191,6 @@
     private InputMethodManagerCompatWrapper mImm;
     private Resources mResources;
     private SharedPreferences mPrefs;
-    private String mInputMethodId;
     private KeyboardSwitcher mKeyboardSwitcher;
     private SubtypeSwitcher mSubtypeSwitcher;
     private VoiceProxy mVoiceProxy;
@@ -177,28 +198,11 @@
     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 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;
@@ -210,11 +214,9 @@
     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;
@@ -241,9 +243,8 @@
         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_SET_BIGRAM_PREDICTIONS = 6;
+        private static final int MSG_PENDING_IMS_CALLBACK = 7;
 
         private int mDelayBeforeFadeoutLanguageOnSpacebar;
         private int mDelayUpdateSuggestions;
@@ -251,7 +252,6 @@
         private int mDurationOfFadeoutLanguageOnSpacebar;
         private float mFinalFadeoutFactorOfLanguageOnSpacebar;
         private long mDoubleSpacesTurnIntoPeriodTimeout;
-        private long mIgnoreSpecialKeyTimeout;
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -271,8 +271,6 @@
                     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
@@ -295,19 +293,15 @@
                         || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
                 break;
             case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null) {
-                    inputView.setSpacebarTextFadeFactor(
-                            (1.0f + mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
-                            (LatinKeyboard)msg.obj);
-                }
+                setSpacebarTextFadeFactor(inputView,
+                        (1.0f + mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
+                        (Keyboard)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);
-                }
+                setSpacebarTextFadeFactor(inputView, mFinalFadeoutFactorOfLanguageOnSpacebar,
+                        (Keyboard)msg.obj);
                 break;
             }
         }
@@ -347,21 +341,32 @@
             sendMessage(obtainMessage(MSG_VOICE_RESULTS));
         }
 
+        private static void setSpacebarTextFadeFactor(LatinKeyboardView inputView,
+                float fadeFactor, Keyboard oldKeyboard) {
+            if (inputView == null) return;
+            final Keyboard keyboard = inputView.getKeyboard();
+            if (keyboard == oldKeyboard) {
+                inputView.updateSpacebar(fadeFactor,
+                        SubtypeSwitcher.getInstance().needsToDisplayLanguage(
+                                keyboard.mId.mLocale));
+            }
+        }
+
         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();
+                final Keyboard keyboard = latinIme.mKeyboardSwitcher.getKeyboard();
                 // 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);
+                    setSpacebarTextFadeFactor(inputView,
+                            needsToDisplayLanguage ? 1.0f : mFinalFadeoutFactorOfLanguageOnSpacebar,
+                                    keyboard);
                 }
                 // The fadeout animation will start when the delay is positive.
                 if (localeChanged && mDelayBeforeFadeoutLanguageOnSpacebar > 0) {
@@ -384,21 +389,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 mHasPendingStartInput;
         private boolean mHasPendingFinishInputView;
         private boolean mHasPendingFinishInput;
+        private EditorInfo mAppliedEditorInfo;
 
         public void startOrientationChanging() {
             removeMessages(MSG_PENDING_IMS_CALLBACK);
@@ -416,18 +413,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;
@@ -438,27 +435,29 @@
                     mPendingSuccesiveImsCallback = 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 (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, editorInfo, restarting);
+                latinIme.onStartInputViewInternal(editorInfo, restarting);
+                mAppliedEditorInfo = editorInfo;
+            }
         }
 
         public void onFinishInputView(boolean finishingInput) {
@@ -468,6 +467,7 @@
             } else {
                 final LatinIME latinIme = getOuterInstance();
                 latinIme.onFinishInputViewInternal(finishingInput);
+                mAppliedEditorInfo = null;
             }
         }
 
@@ -492,12 +492,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);
@@ -509,6 +508,8 @@
 
         loadSettings();
 
+        // TODO: remove the following when it's not needed by updateCorrectionMode() any more
+        mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -546,10 +547,8 @@
     /* 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());
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
-        updateSoundEffectVolume();
-        updateKeypressVibrationDuration();
     }
 
     private void initSuggest() {
@@ -574,7 +573,7 @@
 
         mUserDictionary = new UserDictionary(this, localeStr);
         mSuggest.setUserDictionary(mUserDictionary);
-        mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
+        mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
 
         resetContactsDictionary(oldContactsDictionary);
 
@@ -695,13 +694,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 +713,21 @@
         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"
+            Log.d(TAG, "onStartInputView: editorInfo:" + ((editorInfo == null) ? "none"
                     : String.format("inputType=0x%08x imeOptions=0x%08x",
-                            attribute.inputType, attribute.imeOptions)));
+                            editorInfo.inputType, editorInfo.imeOptions)));
         }
+        LatinImeLogger.onStartInputView(editorInfo);
         // In landscape mode, this method gets called without the input view being created.
         if (inputView == null) {
             return;
@@ -736,47 +736,44 @@
         // 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;
+        final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
         voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(inputType)
                 || InputTypeCompatUtils.isVisiblePasswordInputType(inputType));
 
         // The EditorInfo might have a flag that affects fullscreen mode.
         // Note: This call should be done by InputMethodService?
         updateFullscreenMode();
-        initializeInputAttributes(attribute);
+        mInputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+        mApplicationSpecifiedCompletions = null;
 
         inputView.closing();
         mEnteredText = null;
-        mComposingStringBuilder.setLength(0);
-        mHasUncommittedTypedChars = false;
+        mWordComposer.reset();
         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,6 +782,7 @@
                 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);
@@ -795,73 +793,6 @@
         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();
@@ -923,12 +854,22 @@
                 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
         final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
         if (!mExpectingUpdateSelection) {
-            if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
+            // 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 candidatesCleared to further reduce the impact.
+            if (SPACE_STATE_WEAK == mSpaceState) {
+                // Test for no WEAK_SPACE action because there is a race condition that may end up
+                // in coming here on a normal key press. We set this to NONE because after
+                // a cursor move, we don't want the suggestion strip to swap the space with the
+                // newly inserted punctuation.
+                mSpaceState = SPACE_STATE_NONE;
+            }
+            if (((mWordComposer.isComposingWord())
                     || mVoiceProxy.isVoiceInputHighlighted())
                     && (selectionChanged || candidatesCleared)) {
-                mComposingStringBuilder.setLength(0);
-                mHasUncommittedTypedChars = false;
-                TextEntryState.reset();
+                mWordComposer.reset();
                 updateSuggestions();
                 final InputConnection ic = getCurrentInputConnection();
                 if (ic != null) {
@@ -936,26 +877,23 @@
                 }
                 mComposingStateManager.onFinishComposingText();
                 mVoiceProxy.setVoiceInputHighlighted(false);
-            } else if (!mHasUncommittedTypedChars) {
-                TextEntryState.reset();
+            } else if (!mWordComposer.isComposingWord()) {
+                mWordComposer.reset();
                 updateSuggestions();
             }
-            mJustAddedMagicSpace = false; // The user moved the cursor.
-            mJustReplacedDoubleSpace = false;
         }
         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
@@ -1011,7 +949,7 @@
                 }
             }
         }
-        if (mApplicationSpecifiedCompletionOn) {
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn) {
             mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
             if (applicationSpecifiedCompletions == null) {
                 clearSuggestions();
@@ -1024,7 +962,7 @@
                     .setHasMinimalSuggestion(false);
             // When in fullscreen mode, show completions generated by the application
             setSuggestions(builder.build());
-            mBestWord = null;
+            mWordComposer.deleteAutoCorrection();
             setSuggestionStripShown(true);
         }
     }
@@ -1085,8 +1023,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
@@ -1142,15 +1082,14 @@
     }
 
     public void commitTyped(final InputConnection ic) {
-        if (!mHasUncommittedTypedChars) return;
-        mHasUncommittedTypedChars = false;
-        if (mComposingStringBuilder.length() > 0) {
+        if (!mWordComposer.isComposingWord()) return;
+        final CharSequence typedWord = mWordComposer.getTypedWord();
+        mWordComposer.onCommitWord(WordComposer.COMMIT_TYPE_USER_TYPED_WORD);
+        if (typedWord.length() > 0) {
             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,25 +1105,22 @@
         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))
@@ -1192,22 +1128,19 @@
                 && 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) {
+    // "ic" must not be null
+    private static 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);
+        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_PERIOD
                 && text.charAt(0) == Keyboard.CODE_PERIOD) {
@@ -1215,11 +1148,10 @@
         }
     }
 
-    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 +1167,15 @@
         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 (Utils.hasMultipleEnabledIMEsOrSubtypes(false /* exclude aux subtypes */)) {
             showOptionsMenu();
         } else {
             launchSettings();
@@ -1256,17 +1184,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 (Utils.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,6 +1207,29 @@
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
+    private void insertPunctuationFromSuggestionStrip(final InputConnection ic, final int code) {
+        final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : null;
+        final int toLeft = TextUtils.isEmpty(beforeText) ? 0 : beforeText.charAt(0);
+        final boolean shouldRegisterSwapPunctuation;
+        // If we have a space left of the cursor and it's a weak or a magic space, then we should
+        // swap it, and override the space state with SPACESTATE_SWAP_PUNCTUATION.
+        // To swap it, we fool handleSeparator to think the previous space state was a
+        // magic space.
+        if (Keyboard.CODE_SPACE == toLeft && mSpaceState == SPACE_STATE_WEAK
+                && mSettingsValues.isMagicSpaceSwapper(code)) {
+            mSpaceState = SPACE_STATE_MAGIC;
+            shouldRegisterSwapPunctuation = true;
+        } else {
+            shouldRegisterSwapPunctuation = false;
+        }
+        onCodeInput(code, new int[] { code },
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+        if (shouldRegisterSwapPunctuation) {
+            mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
+        }
+    }
+
     // Implementation of {@link KeyboardActionListener}.
     @Override
     public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
@@ -1284,55 +1239,39 @@
         }
         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;
+
+        // TODO: Consolidate the double space timer, mLastKeyTime, and the space state.
+        if (primaryCode != Keyboard.CODE_SPACE) {
+            mHandler.cancelDoubleSpacesTimer();
+        }
+
         switch (primaryCode) {
         case Keyboard.CODE_DELETE:
-            handleBackspace(lastStateOfJustReplacedDoubleSpace);
+            mSpaceState = SPACE_STATE_NONE;
+            handleBackspace(spaceState);
             mDeleteCount++;
             mExpectingUpdateSelection = 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;
+            onSettingsKeyPressed();
             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);
+            // Caps lock code is handled in KeyboardSwitcher.onCodeInput() below.
+            hapticAndAudioFeedback(primaryCode);
             break;
         case Keyboard.CODE_SHORTCUT:
-            if (!mHandler.isIgnoringSpecialKey()) {
-                mSubtypeSwitcher.switchToShortcutIME();
-            }
-            shouldStartKeyTypedTimer = false;
+            mSubtypeSwitcher.switchToShortcutIME();
             break;
         case Keyboard.CODE_TAB:
             handleTab();
@@ -1346,20 +1285,18 @@
             // To sum it up: do not update mExpectingUpdateSelection here.
             break;
         default:
+            mSpaceState = SPACE_STATE_NONE;
             if (mSettingsValues.isWordSeparator(primaryCode)) {
-                handleSeparator(primaryCode, x, y);
+                handleSeparator(primaryCode, x, y, spaceState);
             } else {
-                handleCharacter(primaryCode, keyCodes, x, y);
+                handleCharacter(primaryCode, keyCodes, x, y, spaceState);
             }
             mExpectingUpdateSelection = true;
             break;
         }
-        switcher.onKey(primaryCode);
+        switcher.onCodeInput(primaryCode);
         // Reset after any single keystroke
         mEnteredText = null;
-        if (shouldStartKeyTypedTimer) {
-            mHandler.startKeyTypedTimer();
-        }
     }
 
     @Override
@@ -1373,10 +1310,10 @@
         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();
+        mWordComposer.reset();
     }
 
     @Override
@@ -1385,77 +1322,94 @@
         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.postUpdateShiftKeyState();
+
+        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 {
+            // We should be very careful about auto-correction cancellation and suggestion
+            // resuming here. The behavior needs to be different according to text field types,
+            // and it would be much clearer to test for them explicitly here rather than
+            // relying on implicit values like "whether the suggestion strip is displayed".
+            if (mWordComposer.didAutoCorrectToAnotherWord()) {
+                Utils.Stats.onAutoCorrectionCancellation();
+                cancelAutoCorrect(ic);
                 return;
             }
-        }
 
-        if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
-            ic.deleteSurroundingText(mEnteredText.length(), 0);
-        } else if (deleteChar) {
+            if (SPACE_STATE_DOUBLE == spaceState) {
+                if (revertDoubleSpace(ic)) {
+                    // No need to reset mSpaceState, it has already be done (that's why we
+                    // receive it as a parameter)
+                    return;
+                }
+            } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+                if (revertSwapPunctuation(ic)) {
+                    // Likewise
+                    return;
+                }
+            }
+
+            // See the comment above: must be careful about resuming auto-suggestion.
             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
+                // NOTE: In general, 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);
+                restartSuggestionsOnManuallyPickedTypedWord(ic);
             } else {
-                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+                ic.deleteSurroundingText(1, 0);
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
-                    sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+                    ic.deleteSurroundingText(1, 0);
+                }
+                if (isSuggestionsRequested()) {
+                    restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
                 }
             }
         }
-        ic.endBatchEdit();
     }
 
     private void handleTab() {
@@ -1481,19 +1435,34 @@
         }
     }
 
-    private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
+    private void handleCharacter(final int primaryCode, final int[] keyCodes, 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, keyCodes, x, y, spaceState, ic);
+        if (null != ic) ic.endBatchEdit();
+    }
 
-        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
-            removeTrailingSpace();
+    // "ic" may be null without this crashing, but the behavior will be really strange
+    private void handleCharacterWhileInBatchEdit(final int primaryCode, final int[] keyCodes,
+            final int x, final int y, final int spaceState, final InputConnection ic) {
+        if (SPACE_STATE_MAGIC == spaceState
+                && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+            if (null != ic) removeTrailingSpaceWhileInBatchEdit(ic);
         }
 
+        boolean isComposingWord = mWordComposer.isComposingWord();
         int code = primaryCode;
         if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
                 && isSuggestionsRequested() && !isCursorTouchingWord()) {
-            if (!mHasUncommittedTypedChars) {
-                mHasUncommittedTypedChars = true;
-                mComposingStringBuilder.setLength(0);
+            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 != code);
                 mWordComposer.reset();
                 clearSuggestions();
                 mComposingStateManager.onFinishComposingText();
@@ -1520,39 +1489,34 @@
                 }
             }
         }
-        if (mHasUncommittedTypedChars) {
-            mComposingStringBuilder.append((char) code);
+        if (isComposingWord) {
             mWordComposer.add(code, keyCodes, x, y);
-            final InputConnection ic = getCurrentInputConnection();
             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;
+        if (SPACE_STATE_MAGIC == spaceState
+                && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+            if (null != ic) swapSwapperAndSpaceWhileInBatchEdit(ic);
         }
 
-        switcher.updateShiftState();
-        if (LatinIME.PERF_DEBUG) measureCps();
-        TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
+        if (mSettingsValues.isWordSeparator(code)) {
+            Utils.Stats.onSeparator((char)code, x, y);
+        } else {
+            Utils.Stats.onNonSeparator((char)code, x, y);
+        }
     }
 
-    private void handleSeparator(int primaryCode, int x, int y) {
+    private void handleSeparator(final int primaryCode, final int x, final int y,
+            final int spaceState) {
         mVoiceProxy.handleSeparator();
         mComposingStateManager.onFinishComposingText();
 
@@ -1562,69 +1526,83 @@
             mHandler.postUpdateSuggestions();
         }
 
-        boolean pickedDefault = 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);
             } else {
                 commitTyped(ic);
             }
         }
 
-        if (mJustAddedMagicSpace) {
+        final boolean swapMagicSpace;
+        if (Keyboard.CODE_ENTER == primaryCode && (SPACE_STATE_MAGIC == spaceState
+                || SPACE_STATE_SWAP_PUNCTUATION == spaceState)) {
+            removeTrailingSpaceWhileInBatchEdit(ic);
+            swapMagicSpace = false;
+        } else if (SPACE_STATE_MAGIC == spaceState) {
             if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
-                sendKeyChar((char)primaryCode);
-                swapSwapperAndSpace();
+                swapMagicSpace = true;
             } else {
-                if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
-                sendKeyChar((char)primaryCode);
-                mJustAddedMagicSpace = false;
+                swapMagicSpace = false;
+                if (mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+                    removeTrailingSpaceWhileInBatchEdit(ic);
+                }
             }
         } else {
-            sendKeyChar((char)primaryCode);
+            swapMagicSpace = false;
         }
 
-        if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
-            maybeDoubleSpace();
-        }
+        sendKeyChar((char)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 (swapMagicSpace) {
+                swapSwapperAndSpaceWhileInBatchEdit(ic);
+                mSpaceState = SPACE_STATE_MAGIC;
+            }
+
             // 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();
         }
     }
 
+    private CharSequence getTextWithUnderline(final CharSequence text) {
+        return mComposingStateManager.isAutoCorrectionIndicatorOn()
+                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
+                : mWordComposer.getTypedWord();
+    }
+
     private void handleClose() {
         commitTyped(getCurrentInputConnection());
         mVoiceProxy.handleClose();
@@ -1635,7 +1613,7 @@
     }
 
     public boolean isSuggestionsRequested() {
-        return mIsSettingsSuggestionStripOn
+        return mInputAttributes.mIsSettingsSuggestionStripOn
                 && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
     }
 
@@ -1653,11 +1631,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();
     }
@@ -1698,18 +1676,21 @@
                     mComposingStateManager.isAutoCorrectionIndicatorOn();
             final boolean newAutoCorrectionIndicator = Utils.willAutoCorrect(words);
             if (oldAutoCorrectionIndicator != newAutoCorrectionIndicator) {
-                if (LatinImeLogger.sDBG) {
+                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
+                if (DEBUG) {
                     Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator
                             + " -> " + newAutoCorrectionIndicator);
+                    if (newAutoCorrectionIndicator
+                            != mComposingStateManager.isAutoCorrectionIndicatorOn()) {
+                        throw new RuntimeException("Couldn't flip the indicator! We are not "
+                                + "composing a word right now.");
+                    }
                 }
-                final CharSequence textWithUnderline = newAutoCorrectionIndicator
-                        ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                this, mComposingStringBuilder)
-                        : mComposingStringBuilder;
+                final CharSequence textWithUnderline =
+                        getTextWithUnderline(mWordComposer.getTypedWord());
                 if (!TextUtils.isEmpty(textWithUnderline)) {
                     ic.setComposingText(textWithUnderline, 1);
                 }
-                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
             }
         }
     }
@@ -1718,18 +1699,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;
@@ -1739,26 +1723,35 @@
             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());
+        final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(mWordComposer,
+                prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode);
 
-        boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
-        final CharSequence typedWord = wordComposer.getTypedWord();
+        boolean autoCorrectionAvailable = !mInputAttributes.mInputTypeNoAutoCorrect
+                && mSuggest.hasAutoCorrection();
+        final CharSequence typedWord = mWordComposer.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 int quotesCount = mWordComposer.trailingSingleQuotesCount();
         final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
-                mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization());
+                mSuggest.getUnigramDictionaries(),
+                // If the typed string ends with a single quote, for dictionary lookup purposes
+                // we behave as if the single quote was not here. Here, we are looking up the
+                // typed string in the dictionary (to avoid autocorrecting from an existing
+                // word, so for consistency this lookup should be made WITHOUT the trailing
+                // single quote.
+                quotesCount > 0
+                        ? typedWord.subSequence(0, typedWord.length() - quotesCount) : 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();
+        autoCorrectionAvailable &= !mWordComposer.isMostlyCaps();
 
         // 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
@@ -1794,34 +1787,46 @@
         setSuggestions(suggestedWords);
         if (suggestedWords.size() > 0) {
             if (shouldBlockAutoCorrectionBySafetyNet) {
-                mBestWord = typedWord;
+                mWordComposer.setAutoCorrection(typedWord);
             } else if (suggestedWords.hasAutoCorrectionWord()) {
-                mBestWord = suggestedWords.getWord(1);
+                mWordComposer.setAutoCorrection(suggestedWords.getWord(1));
             } else {
-                mBestWord = typedWord;
+                mWordComposer.setAutoCorrection(typedWord);
             }
         } else {
-            mBestWord = null;
+            // TODO: replace with mWordComposer.deleteAutoCorrection()?
+            mWordComposer.setAutoCorrection(null);
         }
         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, WordComposer.COMMIT_TYPE_DECIDED_WORD);
             // 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
@@ -1831,18 +1836,17 @@
         mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
                 mSettingsValues.mWordSeparators);
 
-        final boolean recorrecting = TextEntryState.isRecorrecting();
         final InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
         }
-        if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn
+                && mApplicationSpecifiedCompletions != null
                 && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
             if (ic != null) {
                 final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
                 ic.commitCompletion(completionInfo);
             }
-            mCommittedLength = suggestion.length();
             if (mSuggestionsView != null) {
                 mSuggestionsView.clear();
             }
@@ -1861,36 +1865,28 @@
             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
+            // for punctuation entered through the suggestion strip, it should be swapped
+            // if it was a magic or a weak 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 Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
             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;
+            insertPunctuationFromSuggestionStrip(ic, primaryCode);
+            // TODO: the following endBatchEdit seems useless, check
             if (ic != null) {
                 ic.endBatchEdit();
             }
             return;
         }
-        if (!mHasUncommittedTypedChars) {
-            // If we are not composing a word, then it was a suggestion inferred from
-            // context - no user input. We should reset the word composer.
-            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, suggestions.mWords);
         mExpectingUpdateSelection = true;
-        commitBestWord(suggestion);
+        commitChosenWord(suggestion, WordComposer.COMMIT_TYPE_MANUAL_PICK);
         // Add the word to the auto dictionary if it's not a known word
         if (index == 0) {
             addToUserUnigramAndBigramDictionaries(suggestion,
@@ -1898,11 +1894,8 @@
         } 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) {
+        if (mInputAttributes.mInsertSpaceOnPickSuggestionManually) {
             sendMagicSpace();
         }
 
@@ -1922,13 +1915,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,10 +1924,10 @@
             // 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();
             }
@@ -1952,10 +1940,7 @@
     /**
      * 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 InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators);
@@ -1967,8 +1952,11 @@
                 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
+        // WordComposer#didAutoCorrectToAnotherWord by string equality of the remembered
+        // strings.
+        mWordComposer.onCommitWord(commitType);
     }
 
     private static final WordComposer sEmptyWordComposer = new WordComposer();
@@ -1984,7 +1972,7 @@
         final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
                 mSettingsValues.mWordSeparators);
         SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(sEmptyWordComposer,
-                prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
+                prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode);
 
         if (builder.size() > 0) {
             // Explicitly supply an empty typed word (the no-second-arg version of
@@ -2070,54 +2058,143 @@
         return false;
     }
 
-    // "ic" must not null
-    private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
+    // "ic" must not be null
+    private static boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
         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());
+        mComposingStateManager.onStartComposingText();
+        ic.deleteSurroundingText(word.length(), 0);
+        ic.setComposingText(word, 1);
+        mHandler.postUpdateSuggestions();
+    }
+
+    // "ic" must not be null
+    private void cancelAutoCorrect(final InputConnection ic) {
+        mWordComposer.resumeSuggestionOnKeptWord();
+        final String originallyTypedWord = mWordComposer.getTypedWord();
+        final CharSequence autoCorrectedTo = mWordComposer.getAutoCorrectionOrNull();
+        final int cancelLength = autoCorrectedTo.length();
+        final CharSequence separator = ic.getTextBeforeCursor(1, 0);
+        if (DEBUG) {
+            final String wordBeforeCursor =
+                    ic.getTextBeforeCursor(cancelLength + 1, 0).subSequence(0, cancelLength)
+                    .toString();
+            if (!TextUtils.equals(autoCorrectedTo, wordBeforeCursor)) {
+                throw new RuntimeException("cancelAutoCorrect check failed: we thought we were "
+                        + "reverting \"" + autoCorrectedTo
+                        + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
+            }
+            if (TextUtils.equals(originallyTypedWord, wordBeforeCursor)) {
+                throw new RuntimeException("cancelAutoCorrect check failed: we wanted to cancel "
+                        + "auto correction and revert to \"" + originallyTypedWord
+                        + "\" but we found this very string before the cursor");
+            }
         }
+        ic.deleteSurroundingText(cancelLength + 1, 0);
+        ic.commitText(originallyTypedWord, 1);
+        // Re-insert the separator
+        ic.commitText(separator, 1);
+        mWordComposer.deleteAutoCorrection();
+        mWordComposer.onCommitWord(WordComposer.COMMIT_TYPE_CANCEL_AUTO_CORRECT);
+        Utils.Stats.onSeparator(separator.charAt(0), WordComposer.NOT_A_COORDINATE,
+                WordComposer.NOT_A_COORDINATE);
         mHandler.cancelUpdateBigramPredictions();
         mHandler.postUpdateSuggestions();
     }
 
-    // "ic" must not null
+    // "ic" must not be null
+    private void restartSuggestionsOnManuallyPickedTypedWord(final InputConnection ic) {
+        // Note: this relies on the last word still being held in the WordComposer, in
+        // the field for suggestion resuming.
+        // Note: in the interest of code simplicity, we may want to just call
+        // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
+        // the old WordComposer allows to reuse the actual typed coordinates.
+        mWordComposer.resumeSuggestionOnKeptWord();
+        // We resume suggestion, and then we want to set the composing text to the content
+        // of the word composer again. But since we just manually picked a word, there is
+        // no composing text at the moment, so we have to delete the word before we set a
+        // new composing text.
+        final int restartLength = mWordComposer.size();
+        if (DEBUG) {
+            final String wordBeforeCursor =
+                ic.getTextBeforeCursor(restartLength + 1, 0).subSequence(0, restartLength)
+                .toString();
+            if (!TextUtils.equals(mWordComposer.getTypedWord(), wordBeforeCursor)) {
+                throw new RuntimeException("restartSuggestionsOnManuallyPickedTypedWord "
+                        + "check failed: we thought we were reverting \""
+                        + mWordComposer.getTypedWord()
+                        + "\", but before the cursor we found \""
+                        + wordBeforeCursor + "\"");
+            }
+        }
+        // Warning: this +1 takes into account the extra space added by the manual pick process.
+        ic.deleteSurroundingText(restartLength + 1, 0);
+        ic.setComposingText(mWordComposer.getTypedWord(), 1);
+        mHandler.cancelUpdateBigramPredictions();
+        mHandler.postUpdateSuggestions();
+    }
+
+    // "ic" must not be null
     private boolean revertDoubleSpace(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))
-            return false;
+        if (!". ".equals(textBeforeCursor)) {
+            // We should not have come here if we aren't just after a ". ".
+            throw new RuntimeException("Tried to revert double-space combo but we didn't find "
+                    + "\". \" just before the cursor.");
+        }
         ic.beginBatchEdit();
         ic.deleteSurroundingText(2, 0);
         ic.commitText("  ", 1);
@@ -2125,13 +2202,31 @@
         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 (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1)) {
+            // We should not have come here if the text before the cursor is not a space.
+            throw new RuntimeException("Tried to revert a swap of punctuation but we didn't "
+                    + "find a space just before the cursor.");
+        }
+        ic.beginBatchEdit();
+        ic.deleteSurroundingText(2, 0);
+        ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
+        ic.endBatchEdit();
+        return true;
+    }
+
     public boolean isWordSeparator(int code) {
         return mSettingsValues.isWordSeparator(code);
     }
 
     private void sendMagicSpace() {
         sendKeyChar((char)Keyboard.CODE_SPACE);
-        mJustAddedMagicSpace = true;
+        mSpaceState = SPACE_STATE_MAGIC;
         mKeyboardSwitcher.updateShiftState();
     }
 
@@ -2157,33 +2252,23 @@
         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();
-        } else {
-            switcher.onOtherKeyPressed();
-        }
+    private void hapticAndAudioFeedback(int primaryCode) {
+        vibrate();
+        playKeyClick(primaryCode);
     }
 
     @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) {
+        final KeyboardSwitcher switcher = mKeyboardSwitcher;
+        if (switcher.isVibrateAndSoundFeedbackRequired()) {
+            hapticAndAudioFeedback(primaryCode);
         }
+        switcher.onPressKey(primaryCode);
+    }
+
+    @Override
+    public void onReleaseKey(int primaryCode, boolean withSliding) {
+        mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
     }
 
 
@@ -2200,11 +2285,6 @@
         }
     };
 
-    // update keypress sound volume
-    private void updateSoundEffectVolume() {
-        mFxVolume = Utils.getCurrentKeypressSoundVolume(mPrefs, mResources);
-    }
-
     // update flags for silent mode
     private void updateRingerMode() {
         if (mAudioManager == null) {
@@ -2214,10 +2294,6 @@
         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
@@ -2242,7 +2318,7 @@
                 sound = AudioManager.FX_KEYPRESS_STANDARD;
                 break;
             }
-            mAudioManager.playSoundEffect(sound, mFxVolume);
+            mAudioManager.playSoundEffect(sound, mSettingsValues.mFxVolume);
         }
     }
 
@@ -2250,7 +2326,7 @@
         if (!mSettingsValues.mVibrateOn) {
             return;
         }
-        if (mKeypressVibrationDuration < 0) {
+        if (mSettingsValues.mKeypressVibrationDuration < 0) {
             // Go ahead with the system default
             LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
             if (inputView != null) {
@@ -2259,12 +2335,12 @@
                         HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
             }
         } else if (mVibrator != null) {
-            mVibrator.vibrate(mKeypressVibrationDuration);
+            mVibrator.vibrate(mSettingsValues.mKeypressVibrationDuration);
         }
     }
 
-    public WordComposer getCurrentWord() {
-        return mWordComposer;
+    public boolean isAutoCapitalized() {
+        return mWordComposer.isAutoCapitalized();
     }
 
     boolean isSoundOn() {
@@ -2274,22 +2350,14 @@
     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 +2396,8 @@
                 switch (position) {
                 case 0:
                     Intent intent = CompatUtils.getInputLanguageSelectionIntent(
-                            mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
+                            Utils.getInputMethodId(mImm, getPackageName()),
+                            Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                     startActivity(intent);
@@ -2377,35 +2446,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..6f1adfe 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,11 +16,12 @@
 
 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 com.android.inputmethod.latin.Dictionary.DataType;
 
 import java.util.List;
 
@@ -28,12 +29,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() {
@@ -44,7 +46,7 @@
 
     public static void logOnManualSuggestion(
             String before, String after, int position, List<CharSequence> suggestions) {
-   }
+    }
 
     public static void logOnAutoCorrection(String before, String after, int separatorCode) {
     }
@@ -67,6 +69,9 @@
     public static void logOnWarning(String warning) {
     }
 
+    public static void onStartInputView(EditorInfo editorInfo) {
+    }
+
     public static void onStartSuggestion(CharSequence previousWords) {
     }
 
@@ -78,4 +83,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..5af2145 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -38,7 +38,6 @@
 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;
@@ -46,12 +45,10 @@
 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,256 +58,40 @@
 
     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_POPUP_ON = "popup_on";
     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_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_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;
@@ -363,9 +144,9 @@
         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);
+        mVoicePreference = (ListPreference) findPreference(PREF_VOICE_MODE);
         mShowSettingsKeyPreference = (CheckBoxPreference) findPreference(PREF_SHOW_SETTINGS_KEY);
         mShowCorrectionSuggestionsPreference =
                 (ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
@@ -373,12 +154,12 @@
         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,13 +172,13 @@
         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);
+                (PreferenceGroup) findPreference(PREF_MISC_SETTINGS);
 
-        if (!Values.isShowSettingsKeyOption(res)) {
+        if (!SettingsValues.isShowSettingsKeyOptionEnabled(res)) {
             generalSettings.removePreference(mShowSettingsKeyPreference);
         }
 
@@ -412,13 +193,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(
@@ -444,7 +225,8 @@
         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 +237,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 +312,20 @@
     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));
             }
         }
         ensureConsistencyOfAutoCorrectionSettings();
-        mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
+        mVoiceOn = !(prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
                 .equals(mVoiceModeOff));
         updateVoiceModeSummary();
         updateShowCorrectionSuggestionsSummary();
@@ -660,7 +451,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 +467,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 +479,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 +508,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 +538,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 +565,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..0ae28d3
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -0,0 +1,342 @@
+/*
+ * 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.InputTypeCompatUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+public class SettingsValues {
+    private static final String TAG = SettingsValues.class.getSimpleName();
+
+    // From resources:
+    public final int mDelayUpdateOldSuggestions;
+    public final String mMagicSpaceStrippers;
+    public final String mMagicSpaceSwappers;
+    public final String mSuggestPuncs;
+    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 boolean mShowSettingsKey;
+    private final String mVoiceMode;
+    private final String mAutoCorrectionThresholdRawValue;
+    public final String mShowSuggestionsSetting;
+    @SuppressWarnings("unused") // TODO: Use this
+    private final boolean mUsabilityStudyMode;
+    @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);
+        mMagicSpaceStrippers = res.getString(R.string.magic_space_stripping_symbols);
+        mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
+        if (LatinImeLogger.sDBG) {
+            for (int i = 0; i < mMagicSpaceStrippers.length(); ++i) {
+                if (isMagicSpaceSwapper(mMagicSpaceStrippers.codePointAt(i))) {
+                    throw new RuntimeException("Char code " + mMagicSpaceStrippers.codePointAt(i)
+                            + " is both a magic space swapper and stripper.");
+                }
+            }
+        }
+        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);
+        mSymbolsExcludedFromWordSeparators =
+                res.getString(R.string.symbols_excluded_from_word_separators);
+        mWordSeparators = createWordSeparators(mMagicSpaceStrippers, mMagicSpaceSwappers,
+                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);
+        mShowSettingsKey = isSettingsKeyShown(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);
+        mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
+                Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+                Integer.toString(res.getInteger(R.integer.config_delay_after_preview)));
+        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) {
+        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();
+    }
+
+    private static String createWordSeparators(final String magicSpaceStrippers,
+            final String magicSpaceSwappers, final String symbolsExcludedFromWordSeparators,
+            final Resources res) {
+        String wordSeparators = magicSpaceStrippers + magicSpaceSwappers
+                + res.getString(R.string.magic_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 isSettingsKeyShown(final SharedPreferences prefs, final Resources res) {
+        final boolean defaultShowSettingsKey = res.getBoolean(
+                R.bool.config_default_show_settings_key);
+        return isShowSettingsKeyOptionEnabled(res)
+                ? prefs.getBoolean(Settings.PREF_SHOW_SETTINGS_KEY, defaultShowSettingsKey)
+                : defaultShowSettingsKey;
+    }
+
+    public static boolean isShowSettingsKeyOptionEnabled(final Resources resources) {
+        // TODO: Read this once and for all into a public final member
+        return resources.getBoolean(R.bool.config_enable_show_settings_key_option);
+    }
+
+    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 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(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_delay_after_preview))));
+    }
+
+    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 isSettingsKeyEnabled() {
+        return mShowSettingsKey;
+    }
+
+    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 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/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 8a48620..f577816 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();
     }
 
     //////////////////////////////////
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index caa5aac..8e0d031 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -20,6 +20,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.ProximityInfo;
 
 import java.io.File;
@@ -42,9 +43,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
@@ -101,13 +101,12 @@
 
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
     ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
-    private CharSequence mTypedWord;
+    private CharSequence mConsideredWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
     private boolean mIsFirstCharCapitalized;
     private boolean mIsAllUpperCase;
-
-    private int mCorrectionMode = CORRECTION_BASIC;
+    private int mTrailingSingleQuotesCount;
 
     public Suggest(final Context context, final int dictionaryResId, final Locale locale) {
         initAsynchronously(context, dictionaryResId, locale);
@@ -116,7 +115,7 @@
     /* 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);
     }
 
@@ -144,7 +143,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 +168,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() {
@@ -255,9 +246,10 @@
      * @return suggested words object.
      */
     public SuggestedWords getSuggestions(final WordComposer wordComposer,
-            final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) {
+            final CharSequence prevWordForBigram, final ProximityInfo proximityInfo,
+            final int correctionMode) {
         return getSuggestedWordBuilder(wordComposer, prevWordForBigram,
-                proximityInfo).build();
+                proximityInfo, correctionMode).build();
     }
 
     private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
@@ -290,25 +282,27 @@
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
     public SuggestedWords.Builder getSuggestedWordBuilder(
             final WordComposer wordComposer, CharSequence prevWordForBigram,
-            final ProximityInfo proximityInfo) {
+            final ProximityInfo proximityInfo, final int correctionMode) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
         mAutoCorrection.init();
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
+        mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
-        // Save a lowercase version of the original word
-        String typedWord = wordComposer.getTypedWord();
+        final String typedWord = wordComposer.getTypedWord();
+        final String consideredWord = mTrailingSingleQuotesCount > 0
+                ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+                : typedWord;
         if (typedWord != null) {
             // Treating USER_TYPED as UNIGRAM suggestion for logging now.
             LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
                     Dictionary.DataType.UNIGRAM);
         }
-        mTypedWord = typedWord;
+        mConsideredWord = consideredWord;
 
-        if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
-                || mCorrectionMode == CORRECTION_BASIC)) {
+        if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
             // At first character typed, search only the bigrams
             Arrays.fill(mBigramScores, 0);
             collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
@@ -321,7 +315,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) {
@@ -330,7 +324,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;
@@ -354,24 +348,41 @@
                 if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
                     continue;
                 final Dictionary dictionary = mUnigramDictionaries.get(key);
-                dictionary.getWords(wordComposer, this, proximityInfo);
+                if (mTrailingSingleQuotesCount > 0) {
+                    final WordComposer tmpWordComposer = new WordComposer(wordComposer);
+                    for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+                        tmpWordComposer.deleteLast();
+                    }
+                    dictionary.getWords(tmpWordComposer, this, proximityInfo);
+                } else {
+                    dictionary.getWords(wordComposer, this, proximityInfo);
+                }
             }
         }
-        final String typedWordString = typedWord == null ? null : typedWord.toString();
+        final String consideredWordString =
+                consideredWord == null ? null : consideredWord.toString();
 
         CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
-                mWhiteListDictionary.getWhitelistedWord(typedWordString));
+                mWhiteListDictionary.getWhitelistedWord(consideredWordString));
 
         mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
-                mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+                mSuggestions, mScores, consideredWord, mAutoCorrectionThreshold, correctionMode,
                 whitelistedWord);
 
         if (whitelistedWord != null) {
-            mSuggestions.add(0, whitelistedWord);
+            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);
+            }
         }
 
         if (typedWord != null) {
-            mSuggestions.add(0, typedWordString);
+            mSuggestions.add(0, typedWord.toString());
         }
         Utils.removeDupes(mSuggestions);
 
@@ -424,7 +435,7 @@
         int pos = 0;
 
         // Check if it's the same word, only caps are different
-        if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
+        if (Utils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
             // TODO: remove this surrounding if clause and move this logic to
             // getSuggestedWordBuilder.
             if (suggestions.size() > 0) {
@@ -486,6 +497,9 @@
         } 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);
@@ -518,7 +532,8 @@
         return -1;
     }
 
-    private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
+    private static void collectGarbage(ArrayList<CharSequence> suggestions,
+            int prefMaxSuggestions) {
         int poolSize = StringBuilderPool.getSize();
         int garbageSize = suggestions.size();
         while (poolSize < prefMaxSuggestions && garbageSize > 0) {
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..f80534c 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
@@ -238,7 +238,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)
@@ -310,7 +310,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);
@@ -380,7 +380,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 +388,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..6d6296e 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,54 +160,19 @@
     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();
+        // 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);
 
         // In case the above does a synchronous callback of the change observer
         setRequiresReload(false);
@@ -242,6 +207,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..a7f57ae 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -149,7 +149,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 +172,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,7 +206,7 @@
         }
     }
 
-    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);
@@ -227,8 +227,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;
@@ -251,7 +251,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..bc8a130 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -17,11 +17,13 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
-import android.content.SharedPreferences;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -37,14 +39,17 @@
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.define.JniLibName;
 
 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;
@@ -57,6 +62,12 @@
     private static boolean DBG = LatinImeLogger.sDBG;
     private static boolean DBG_EDIT_DISTANCE = false;
 
+    // Constants for resource name parsing.
+    public static final char ESCAPE_CHAR = '\\';
+    public static final char PREFIX_AT = '@';
+    public static final char SUFFIX_SLASH = '/';
+    private static final String PREFIX_STRING = PREFIX_AT + "string";
+
     private Utils() {
         // Intentional empty constructor for utility class.
     }
@@ -113,8 +124,9 @@
     }
 
     public static boolean hasMultipleEnabledIMEsOrSubtypes(
-            final InputMethodManagerCompatWrapper imm,
             final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
         final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
 
         // Number of the filtered IMEs
@@ -148,10 +160,21 @@
             }
         }
 
-        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;
+        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(InputMethodManagerCompatWrapper imm, String packageName) {
@@ -185,7 +208,8 @@
         final int typedWordLength = typedWord.length();
         final int maxEditDistanceOfNativeDictionary =
                 (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
-        final int distance = Utils.editDistance(typedWord, suggestionWord);
+        final int distance = BinaryDictionary.editDistance(
+                typedWord.toString(), suggestionWord.toString());
         if (DBG) {
             Log.d(TAG, "Autocorrected edit distance = " + distance
                     + ", " + maxEditDistanceOfNativeDictionary);
@@ -242,7 +266,7 @@
             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;
         }
@@ -317,49 +341,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 +354,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";
@@ -465,7 +397,7 @@
             }
         }
 
-        public void writeBackSpace() {
+        public static void writeBackSpace() {
             UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
         }
 
@@ -504,32 +436,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);
                 }
             });
         }
@@ -630,9 +619,13 @@
 
     public static void loadNativeLibrary() {
         try {
-            System.loadLibrary("jni_latinime");
+            System.loadLibrary(JniLibName.JNI_LIB_NAME);
         } catch (UnsatisfiedLinkError ule) {
-            Log.e(TAG, "Could not load native library jni_latinime");
+            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);
+            }
         }
     }
 
@@ -778,40 +771,90 @@
         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 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 char code, final int x,
+                final int y) {
+            RingCharBuffer.getInstance().push(code, x, y);
+            LatinImeLogger.logOnInputSeparator();
+        }
+
+        public static void onAutoCorrection(final String typedWord, final String correctedWord,
+                final int separatorCode) {
+            if (TextUtils.isEmpty(typedWord)) return;
+            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
+        }
+
+        public static void onAutoCorrectionCancellation() {
+            LatinImeLogger.logOnAutoCorrectionCancelled();
+        }
+    }
+
+    public static 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;
+    }
+
+    public static String resolveStringResource(String text, Resources res, int packageNameResId) {
+        final int size = text.length();
+        if (size < PREFIX_STRING.length()) {
+            return text;
+        }
+
+        StringBuilder 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 = Utils.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) {
+                pos++;
+                if (sb != null) {
+                    sb.append(c);
+                    if (pos < size) {
+                        sb.append(text.charAt(pos));
+                    }
+                }
+            } else if (sb != null) {
+                sb.append(c);
+            }
+        }
+        return (sb == null) ? text : sb.toString();
+    }
+
+    private static int searchResourceNameEnd(String text, int start) {
+        final int size = text.length();
+        if (start >= size || text.charAt(start) != SUFFIX_SLASH) {
+            throw new RuntimeException("Resource name not specified");
+        }
+        for (int pos = start + 1; 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;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index adc5637..e95dcfd 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,14 @@
 
 package com.android.inputmethod.latin;
 
+import android.text.TextUtils;
+
+import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.Keyboard;
 
 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 +33,75 @@
     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;
+    // TODO: Straighten out commit behavior so that the flags here are more understandable,
+    // and possibly adjust their names.
+    // 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;
 
-    private int[] mXCoordinates;
-    private int[] mYCoordinates;
+    // Storage for all the info about the current input.
+    private static class CharacterStore {
+        /**
+         * The list of unicode values for each keystroke (including surrounding keys)
+         */
+        ArrayList<int[]> mCodes;
+        int[] mXCoordinates;
+        int[] mYCoordinates;
+        StringBuilder mTypedWord;
+        CharSequence mAutoCorrection;
+        CharacterStore() {
+            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;
+        }
+        CharacterStore(final CharacterStore that) {
+            mCodes = new ArrayList<int[]>(that.mCodes);
+            mTypedWord = new StringBuilder(that.mTypedWord);
+            mXCoordinates = Arrays.copyOf(that.mXCoordinates, that.mXCoordinates.length);
+            mYCoordinates = Arrays.copyOf(that.mYCoordinates, that.mYCoordinates.length);
+        }
+        void reset() {
+            // For better performance than creating a new character store.
+            mCodes.clear();
+            mTypedWord.setLength(0);
+            mAutoCorrection = null;
+        }
+    }
 
-    private StringBuilder mTypedWord;
+    // The currently typing word. May not be null.
+    private CharacterStore mCurrentWord;
+    // The information being kept for resuming suggestion. May be null if wiped.
+    private CharacterStore mCommittedWordSavedForSuggestionResuming;
 
     private int mCapsCount;
 
     private boolean mAutoCapitalized;
-    
+    // Cache this value for performance
+    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];
+        mCurrentWord = new CharacterStore();
+        mCommittedWordSavedForSuggestionResuming = null;
+        mTrailingSingleQuotesCount = 0;
     }
 
     public WordComposer(WordComposer source) {
@@ -60,23 +109,23 @@
     }
 
     public void init(WordComposer source) {
-        mCodes = new ArrayList<int[]>(source.mCodes);
-        mTypedWord = new StringBuilder(source.mTypedWord);
-        mXCoordinates = source.mXCoordinates;
-        mYCoordinates = source.mYCoordinates;
+        mCurrentWord = new CharacterStore(source.mCurrentWord);
+        mCommittedWordSavedForSuggestionResuming = source.mCommittedWordSavedForSuggestionResuming;
         mCapsCount = source.mCapsCount;
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
+        mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
     }
 
     /**
      * Clear out the keys registered so far.
      */
     public void reset() {
-        mCodes.clear();
-        mTypedWord.setLength(0);
+        mCurrentWord.reset();
+        mCommittedWordSavedForSuggestionResuming = null;
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
+        mTrailingSingleQuotesCount = 0;
     }
 
     /**
@@ -84,7 +133,11 @@
      * @return the number of keystrokes
      */
     public final int size() {
-        return mTypedWord.length();
+        return mCurrentWord.mTypedWord.length();
+    }
+
+    public final boolean isComposingWord() {
+        return size() > 0;
     }
 
     /**
@@ -93,15 +146,15 @@
      * @return the unicode for the pressed and surrounding keys
      */
     public int[] getCodesAt(int index) {
-        return mCodes.get(index);
+        return mCurrentWord.mCodes.get(index);
     }
 
     public int[] getXCoordinates() {
-        return mXCoordinates;
+        return mCurrentWord.mXCoordinates;
     }
 
     public int[] getYCoordinates() {
-        return mYCoordinates;
+        return mCurrentWord.mYCoordinates;
     }
 
     private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) {
@@ -116,16 +169,67 @@
      */
     public void add(int primaryCode, int[] codes, int x, int y) {
         final int newIndex = size();
-        mTypedWord.append((char) primaryCode);
+        mCurrentWord.mTypedWord.append((char) primaryCode);
         correctPrimaryJuxtapos(primaryCode, codes);
-        mCodes.add(codes);
+        mCurrentWord.mCodes.add(codes);
         if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
-            mXCoordinates[newIndex] = x;
-            mYCoordinates[newIndex] = y;
+            mCurrentWord.mXCoordinates[newIndex] = x;
+            mCurrentWord.mYCoordinates[newIndex] = y;
         }
         mIsFirstCharCapitalized = isFirstCharCapitalized(
                 newIndex, primaryCode, mIsFirstCharCapitalized);
         if (Character.isUpperCase(primaryCode)) mCapsCount++;
+        if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+            ++mTrailingSingleQuotesCount;
+        } else {
+            mTrailingSingleQuotesCount = 0;
+        }
+        mCurrentWord.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.getKeyAndNearbyCodes(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) {
+            int codePoint = word.charAt(i);
+            addKeyInfo(codePoint, keyboard, keyDetector);
+        }
+        mCommittedWordSavedForSuggestionResuming = null;
+    }
+
+    /**
+     * 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 +239,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];
@@ -150,25 +254,31 @@
         final int size = size();
         if (size > 0) {
             final int lastPos = size - 1;
-            char lastChar = mTypedWord.charAt(lastPos);
-            mCodes.remove(lastPos);
-            mTypedWord.deleteCharAt(lastPos);
+            char lastChar = mCurrentWord.mTypedWord.charAt(lastPos);
+            mCurrentWord.mCodes.remove(lastPos);
+            mCurrentWord.mTypedWord.deleteCharAt(lastPos);
             if (Character.isUpperCase(lastChar)) mCapsCount--;
         }
         if (size() == 0) {
             mIsFirstCharCapitalized = false;
         }
+        if (mTrailingSingleQuotesCount > 0) {
+            --mTrailingSingleQuotesCount;
+        } else {
+            for (int i = mCurrentWord.mTypedWord.length() - 1; i >= 0; --i) {
+                if (Keyboard.CODE_SINGLE_QUOTE != mCurrentWord.mTypedWord.codePointAt(i)) break;
+                ++mTrailingSingleQuotesCount;
+            }
+        }
+        mCurrentWord.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();
+        return mCurrentWord.mTypedWord.toString();
     }
 
     /**
@@ -179,6 +289,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 +308,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 +324,62 @@
     public boolean isAutoCapitalized() {
         return mAutoCapitalized;
     }
+
+    /**
+     * Sets the auto-correction for this word.
+     */
+    public void setAutoCorrection(final CharSequence correction) {
+        mCurrentWord.mAutoCorrection = correction;
+    }
+
+    /**
+     * Remove any auto-correction that may have been set.
+     */
+    public void deleteAutoCorrection() {
+        mCurrentWord.mAutoCorrection = null;
+    }
+
+    /**
+     * @return the auto-correction for this word, or null if none.
+     */
+    public CharSequence getAutoCorrectionOrNull() {
+        return mCurrentWord.mAutoCorrection;
+    }
+
+    // `type' should be one of the COMMIT_TYPE_* constants above.
+    public void onCommitWord(final int type) {
+        mCommittedWordSavedForSuggestionResuming = mCurrentWord;
+        // Note: currently, we come here whenever we commit a word. If it's any *other* kind than
+        // DECIDED_WORD, we should reset mAutoCorrection so that we don't attempt to cancel later.
+        // If it's a DECIDED_WORD, it may be an actual auto-correction by the IME, or what the user
+        // typed because the IME decided *not* to auto-correct for whatever reason.
+        // Ideally we would also null it when it was a DECIDED_WORD that was not an auto-correct.
+        // As it happens these two cases should behave differently, because the former can be
+        // canceled while the latter can't. Currently, we figure this out in
+        // #didAutoCorrectToAnotherWord with #equals(). It would be marginally cleaner to do it
+        // here, but it would be slower (since we would #equals() for each commit, instead of
+        // only on cancel), and ultimately we want to figure it out even earlier anyway.
+        if (type != COMMIT_TYPE_DECIDED_WORD) {
+            // Only ever revert an auto-correct.
+            mCommittedWordSavedForSuggestionResuming.mAutoCorrection = null;
+        }
+        // TODO: improve performance by swapping buffers instead of creating a new object.
+        mCurrentWord = new CharacterStore();
+    }
+
+    public boolean hasWordKeptForSuggestionResuming() {
+        return null != mCommittedWordSavedForSuggestionResuming;
+    }
+
+    public void resumeSuggestionOnKeptWord() {
+        mCurrentWord = mCommittedWordSavedForSuggestionResuming;
+        mCommittedWordSavedForSuggestionResuming = null;
+    }
+
+    public boolean didAutoCorrectToAnotherWord() {
+        return null != mCommittedWordSavedForSuggestionResuming
+                && !TextUtils.isEmpty(mCommittedWordSavedForSuggestionResuming.mAutoCorrection)
+                && !TextUtils.equals(mCommittedWordSavedForSuggestionResuming.mTypedWord,
+                        mCommittedWordSavedForSuggestionResuming.mAutoCorrection);
+    }
 }
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..d747a02
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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 line " + parser.getLineNumber()
+                    + ", column " + parser.getColumnNumber());
+        }
+    }
+
+    @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..39e47f6 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,6 +27,7 @@
 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;
@@ -41,21 +44,27 @@
 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.Iterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.HashSet;
 
 /**
  * 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 +91,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 +204,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 +216,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 +226,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];
@@ -175,7 +270,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 +293,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) {
@@ -243,16 +338,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 +369,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,7 +393,9 @@
     }
 
     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 DictionaryCollection dictionaryCollection =
@@ -314,11 +414,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);
     }
 
@@ -346,6 +451,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 +465,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,7 +517,7 @@
             // 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
@@ -389,7 +530,7 @@
                 // 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 (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
             }
             // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
             // in this word are letters
@@ -408,7 +549,7 @@
             try {
                 final String text = textInfo.getText();
 
-                if (shouldFilterOut(text)) {
+                if (shouldFilterOut(text, mScript)) {
                     DictAndProximity dictInfo = null;
                     try {
                         dictInfo = mDictionaryPool.takeOrGetNull();
@@ -426,17 +567,23 @@
 
                 // 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 proximityIndex =
+                            SpellCheckerProximityInfo.getIndexOfCodeForScript(character, mScript);
                     final int[] proximities;
                     if (-1 == proximityIndex) {
                         proximities = new int[] { character };
                     } else {
-                        proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
+                        // 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(mScript),
                                 proximityIndex,
                                 proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
                     }
@@ -475,7 +622,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 +630,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..2bc2cfd 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -29,65 +29,150 @@
     // 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 95%
rename from java/src/com/android/inputmethod/latin/MoreSuggestions.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 9a59ef2..3d26d97 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.res.Resources;
 import android.graphics.Paint;
@@ -25,26 +25,27 @@
 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.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 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) {
         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];
@@ -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,
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
similarity index 95%
rename from java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index c61dd63..600f14e 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,13 +56,13 @@
     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
diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
similarity index 85%
rename from java/src/com/android/inputmethod/latin/SuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
index c25ecb3..40d7826 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,12 @@
 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.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.Utils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -73,7 +77,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;
@@ -101,8 +105,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 +119,6 @@
             }
         }
 
-        public void postHidePreview() {
-            cancelHidePreview();
-            sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW);
-        }
-
         public void cancelHidePreview() {
             removeMessages(MSG_HIDE_PREVIEW);
         }
@@ -149,6 +146,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 +170,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 +190,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 +199,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(
@@ -228,7 +230,6 @@
             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);
         }
 
         private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) {
@@ -298,6 +299,8 @@
             final int color;
             if (index == mCenterSuggestionIndex && Utils.willAutoCorrect(suggestions)) {
                 color = mColorAutoCorrect;
+            } else if (index == mCenterSuggestionIndex && suggestions.mTypedWordValid) {
+                color = mColorValidTypedWord;
             } else if (isSuggested) {
                 color = mColorSuggested;
             } else {
@@ -433,7 +436,7 @@
 
                 final TextView word = mWords.get(index);
                 word.setEnabled(true);
-                word.setTextColor(mColorTypedWord);
+                word.setTextColor(mColorAutoCorrect);
                 final CharSequence text = suggestions.getWord(index);
                 word.setText(text);
                 word.setTextScaleX(1.0f);
@@ -445,7 +448,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 +467,98 @@
             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 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;
+            }
+        }
     }
 
     /**
@@ -557,99 +645,15 @@
         mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth());
     }
 
-    private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
-        if (DBG && pos < suggestions.size()) {
-            final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-            if (wordInfo != null) {
-                final CharSequence debugInfo = wordInfo.getDebugString();
-                if (!TextUtils.isEmpty(debugInfo)) {
-                    return debugInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    private static void setLayoutWeight(View v, float weight, int height) {
-        final ViewGroup.LayoutParams lp = v.getLayoutParams();
-        if (lp instanceof LinearLayout.LayoutParams) {
-            final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
-            llp.weight = weight;
-            llp.width = 0;
-            llp.height = height;
-        }
-    }
-
-    private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
-        paint.setTextScaleX(1.0f);
-        final int width = getTextWidth(text, paint);
-        if (width <= maxWidth) {
-            return 1.0f;
-        }
-        return maxWidth / (float)width;
-    }
-
-    private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
-            TextPaint paint) {
-        if (text == null) return null;
-        paint.setTextScaleX(1.0f);
-        final int width = getTextWidth(text, paint);
-        if (width <= maxWidth) {
-            return text;
-        }
-        final float scaleX = maxWidth / (float)width;
-        if (scaleX >= MIN_TEXT_XSCALE) {
-            paint.setTextScaleX(scaleX);
-            return text;
-        }
-
-        // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To get
-        // squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
-        final CharSequence ellipsized = TextUtils.ellipsize(
-                text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
-        paint.setTextScaleX(MIN_TEXT_XSCALE);
-        return ellipsized;
-    }
-
-    private static int getTextWidth(CharSequence text, TextPaint paint) {
-        if (TextUtils.isEmpty(text)) return 0;
-        final Typeface savedTypeface = paint.getTypeface();
-        paint.setTypeface(getTextTypeface(text));
-        final int len = text.length();
-        final float[] widths = new float[len];
-        final int count = paint.getTextWidths(text, 0, len, widths);
-        int width = 0;
-        for (int i = 0; i < count; i++) {
-            width += Math.round(widths[i] + 0.5f);
-        }
-        paint.setTypeface(savedTypeface);
-        return width;
-    }
-
-    private static Typeface getTextTypeface(CharSequence text) {
-        if (!(text instanceof SpannableString))
-            return Typeface.DEFAULT;
-
-        final SpannableString ss = (SpannableString)text;
-        final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
-        if (styles.length == 0)
-            return Typeface.DEFAULT;
-
-        switch (styles[0].getStyle()) {
-        case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
-        // TODO: BOLD_ITALIC, ITALIC case?
-        default: return Typeface.DEFAULT;
-        }
-    }
 
     public boolean isShowingAddToDictionaryHint() {
         return mSuggestionsStrip.getChildCount() > 0
                 && 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() {
@@ -675,34 +679,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 =
@@ -832,8 +810,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;
diff --git a/native/Android.mk b/native/Android.mk
index cf1da4a..5053e7d 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -1,60 +1 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
-
-LOCAL_CFLAGS += -Werror -Wall
-
-# To suppress compiler warnings for unused variables/functions used for debug features etc.
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
-
-LOCAL_SRC_FILES := \
-    jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \
-    jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
-    jni/jni_common.cpp \
-    src/bigram_dictionary.cpp \
-    src/char_utils.cpp \
-    src/correction.cpp \
-    src/dictionary.cpp \
-    src/proximity_info.cpp \
-    src/unigram_dictionary.cpp
-
-#FLAG_DBG := true
-#FLAG_DO_PROFILE := true
-
-TARGETING_UNBUNDLED_FROYO := true
-
-ifeq ($(TARGET_ARCH), x86)
-    TARGETING_UNBUNDLED_FROYO := false
-endif
-
-ifeq ($(FLAG_DBG), true)
-    TARGETING_UNBUNDLED_FROYO := false
-endif
-
-ifeq ($(FLAG_DO_PROFILE), true)
-    TARGETING_UNBUNDLED_FROYO := false
-endif
-
-ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
-    LOCAL_NDK_VERSION := 4
-    LOCAL_SDK_VERSION := 8
-endif
-
-LOCAL_MODULE := libjni_latinime
-
-LOCAL_MODULE_TAGS := 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..53bd21d
--- /dev/null
+++ b/native/jni/Android.mk
@@ -0,0 +1,87 @@
+# 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 := \
+    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 ($(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..6e4fefd 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -28,14 +28,14 @@
 
 namespace latinime {
 
-static jint latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
+static jlong 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,
         jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
         jfloatArray sweetSpotRadiusArray) {
-    jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, NULL);
+    jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
     jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
     jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
     jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
@@ -59,19 +59,19 @@
     safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
     safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
     env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
-    return (jint)proximityInfo;
+    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", "(IIIII[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/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..db7734b 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/src/bigram_dictionary.cpp
@@ -32,8 +32,8 @@
     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;
     }
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..1d74998 100644
--- a/native/src/binary_format.h
+++ b/native/src/binary_format.h
@@ -22,12 +22,12 @@
 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;
@@ -61,7 +61,9 @@
 }
 
 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 +147,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..63dd283 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -16,11 +16,13 @@
 
 #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 "dictionary.h"
 #include "proximity_info.h"
@@ -31,81 +33,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 +114,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 +130,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 +143,8 @@
     mSpaceProximityPos = spaceProximityPos;
     mMissingSpacePos = missingSpacePos;
     mUseFullEditDistance = useFullEditDistance;
+    mDoAutoCompletion = doAutoCompletion;
+    mMaxErrors = maxErrors;
 }
 
 void Correction::checkState() {
@@ -179,16 +165,27 @@
 }
 
 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) {
@@ -229,20 +226,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;
 }
@@ -280,7 +267,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 + 1 >= mInputLength));
 }
 
 void Correction::addCharToCurrentWord(const int32_t c) {
@@ -290,13 +279,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,6 +293,13 @@
     }
 }
 
+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;
 }
@@ -312,12 +307,17 @@
 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 +342,7 @@
         return processSkipChar(c, isTerminal, incremented);
     }
 
+    // Check possible corrections.
     if (mExcessivePos >= 0) {
         if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) {
             mExcessivePos = mOutputIndex;
@@ -384,15 +385,20 @@
             --mTransposedCount;
             if (DEBUG_CORRECTION) {
                 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(
@@ -405,7 +411,7 @@
                 && isEquivalentChar(mProximityInfo->getMatchedProximityId(
                         mInputIndex, mWord[mOutputIndex - 1], false))) {
             if (DEBUG_CORRECTION) {
-                LOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
+                AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
             }
             // Conversion p->e
             // Example:
@@ -482,10 +488,10 @@
         } else {
             if (DEBUG_CORRECTION) {
                 DUMP_WORD(mWord, mOutputIndex);
-                LOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                         mTransposedCount, mExcessiveCount, c);
             }
-            return UNRELATED;
+            return processUnrelatedCorrectionType();
         }
     } else if (secondTransposing) {
         // If inputIndex is greater than mInputLength, that means there is no
@@ -535,11 +541,13 @@
         mTerminalOutputIndex = mOutputIndex - 1;
         if (DEBUG_CORRECTION) {
             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;
     }
 }
@@ -607,13 +615,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 +624,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 +651,50 @@
     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;
 
     // 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 {
+        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, &finalFreq);
+                } else {
+                    multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+                }
+            } else if (ed == 0) {
                 multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+                sameLength = true;
             }
-        } 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);
     } else {
-        // TODO: Calculate the edit distance for transposed char
         const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
         multiplyIntCapped(matchWeight, &finalFreq);
     }
@@ -707,7 +714,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);
     }
@@ -721,7 +728,7 @@
         multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
         if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) {
             if (DEBUG_CORRECTION_FREQ) {
-                LOGI("Double excessive demotion");
+                AKLOGI("Double excessive demotion");
             }
             // If an excessive character is not adjacent to the left char or the right char,
             // we will demote this word.
@@ -771,7 +778,7 @@
         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);
@@ -787,7 +794,8 @@
     // 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) {
             finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
         }
     }
@@ -832,14 +840,14 @@
     }
 
     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);
+        DUMP_WORD(correction->mWord, outputLength);
+        AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
+                skippedCount, transposedCount, excessiveCount, outputLength, lastCharExceeded,
+                sameLength, quoteDiffCount, ed, finalFreq);
     }
 
     return finalFreq;
@@ -878,7 +886,103 @@
             firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion;
 
     if (DEBUG_DICT_FULL) {
-        LOGI("Two words: %c, %c, %d", word[0], word[firstWordLength + 1], capitalizedWordDemotion);
+        AKLOGI("Two words: %c, %c, %d",
+                word[0], word[firstWordLength + 1], capitalizedWordDemotion);
+    }
+
+    if (firstWordLength == 0 || secondWordLength == 0) {
+        return 0;
+    }
+    const int firstDemotionRate = 100 - TWO_WORDS_CORRECTION_DEMOTION_BASE / (firstWordLength + 1);
+    int tempFirstFreq = firstFreq;
+    multiplyRate(firstDemotionRate, &tempFirstFreq);
+
+    const int secondDemotionRate = 100
+            - TWO_WORDS_CORRECTION_DEMOTION_BASE / (secondWordLength + 1);
+    int tempSecondFreq = secondFreq;
+    multiplyRate(secondDemotionRate, &tempSecondFreq);
+
+    const int totalLength = firstWordLength + secondWordLength;
+
+    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
+    // length.
+    int totalFreq = tempFirstFreq + tempSecondFreq;
+
+    // This is a workaround to try offsetting the not-enough-demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
+    // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
+    // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
+    const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
+    multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
+
+    // At this moment, totalFreq is calculated by the following formula:
+    // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
+    //        * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
+
+    multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq);
+
+    // This is another workaround to offset the demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
+    // the same amount because we already have adjusted the synthetic freq of this "missing or
+    // mistyped space" suggestion candidate above in this method.
+    const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
+    multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
+
+    if (isSpaceProximity) {
+        // A word pair with one space proximity correction
+        if (DEBUG_DICT) {
+            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 (capitalizedWordDemotion) {
+        multiplyRate(TWO_WORDS_CAPITALIZED_DEMOTION_RATE, &totalFreq);
+    }
+
+    return totalFreq;
+}
+
+/* static */
+int Correction::RankingAlgorithm::calcFreqForSplitTwoWordsOld(
+        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);
+    const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
+
+    bool firstCapitalizedWordDemotion = false;
+    if (firstWordLength >= 2) {
+        firstCapitalizedWordDemotion = isUpperCase(word[0]);
+    }
+
+    bool secondCapitalizedWordDemotion = false;
+    if (secondWordLength >= 2) {
+        secondCapitalizedWordDemotion = isUpperCase(word[firstWordLength + 1]);
+    }
+
+    const bool capitalizedWordDemotion =
+            firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion;
+
+    if (DEBUG_DICT_FULL) {
+        AKLOGI("Two words: %c, %c, %d",
+                word[0], word[firstWordLength + 1], capitalizedWordDemotion);
     }
 
     if (firstWordLength == 0 || secondWordLength == 0) {
@@ -923,7 +1027,7 @@
     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);
@@ -938,4 +1042,103 @@
     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..0715551 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -27,8 +27,7 @@
 class ProximityInfo;
 
 class Correction {
-
-public:
+ public:
     typedef enum {
         TRAVERSE_ALL_ON_TERMINAL,
         TRAVERSE_ALL_NOT_ON_TERMINAL,
@@ -44,11 +43,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();
@@ -76,6 +75,8 @@
     int getFreqForSplitTwoWords(
             const int firstFreq, const int secondFreq, 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 +95,45 @@
     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 calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq,
+                const Correction* correction, const unsigned short *word);
+        static int calcFreqForSplitTwoWordsOld(const int firstFreq, const int secondFreq,
+                const Correction* correction, 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 +141,7 @@
     int mMissingSpacePos;
     int mTerminalInputIndex;
     int mTerminalOutputIndex;
+    int mMaxErrors;
 
     // The following arrays are state buffer.
     unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
@@ -150,13 +176,6 @@
     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/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..119a7d7 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -20,15 +20,32 @@
 
 #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 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;
+    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,8 +59,8 @@
 #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) {
     for (int i = 0; i < PROF_BUF_SIZE; ++i) {
@@ -55,9 +72,9 @@
 
 static 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 +83,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 +115,10 @@
 #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_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
 
@@ -125,8 +131,8 @@
 #define DEBUG_PROXIMITY_INFO false
 #define DEBUG_CORRECTION false
 #define DEBUG_CORRECTION_FREQ false
+#define DEBUG_WORDS_PRIORITY_QUEUE false
 
-#define DUMP_WORD(word, length)
 
 #endif // FLAG_DBG
 
@@ -166,6 +172,7 @@
 #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
 #define NOT_A_INDEX -1
+#define NOT_A_FREQUENCY -1
 
 #define KEYCODE_SPACE ' '
 
@@ -180,10 +187,10 @@
 // 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_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_MATCH_SKIP_PROMOTION_RATE 105
@@ -192,14 +199,26 @@
 #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
 
-// 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
 
+// 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 TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD 0.39
+#define START_TWO_WORDS_CORRECTION_THRESHOLD 0.22
+
 #define MAX_DEPTH_MULTIPLIER 3
 
 // TODO: Reduce this constant if possible; check the maximum number of umlauts in the same German
diff --git a/native/src/dictionary.cpp b/native/src/dictionary.cpp
index a49769b..822c215 100644
--- a/native/src/dictionary.cpp
+++ b/native/src/dictionary.cpp
@@ -33,11 +33,14 @@
     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));
         }
     }
+    mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
+    mWordsPriorityQueuePool = new WordsPriorityQueuePool(
+            maxWords, SUB_QUEUE_MAX_WORDS, maxWordLength);
     mUnigramDictionary = new UnigramDictionary(mDict, typedLetterMultiplier, fullWordMultiplier,
             maxWordLength, maxWords, maxAlternatives, IS_LATEST_DICT_VERSION);
     mBigramDictionary = new BigramDictionary(mDict, maxWordLength, maxAlternatives,
@@ -45,6 +48,8 @@
 }
 
 Dictionary::~Dictionary() {
+    delete mCorrection;
+    delete mWordsPriorityQueuePool;
     delete mUnigramDictionary;
     delete mBigramDictionary;
 }
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..b91957c 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -47,12 +47,12 @@
           HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
                   && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
                   && sweetSpotCenterYs && sweetSpotRadii),
-          mInputXCoordinates(NULL), mInputYCoordinates(NULL),
+          mInputXCoordinates(0), mInputYCoordinates(0),
           mTouchPositionCorrectionEnabled(false) {
     const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
     mProximityCharsArray = new uint32_t[proximityGridLength];
     if (DEBUG_PROXIMITY_INFO) {
-        LOGI("Create proximity info array %d", proximityGridLength);
+        AKLOGI("Create proximity info array %d", proximityGridLength);
     }
     memcpy(mProximityCharsArray, proximityCharsArray,
             proximityGridLength * sizeof(mProximityCharsArray[0]));
@@ -100,13 +100,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;
@@ -167,7 +175,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 +240,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,7 +253,7 @@
     // 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
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 35e354c..9ca5505 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -26,7 +26,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;
@@ -56,7 +56,7 @@
     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 +68,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
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..e998ee4 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 {
 
@@ -46,21 +47,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 an 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,9 +91,10 @@
 // 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,
+        const int codesBufferSize, const int flags, const int *codesSrc,
+        const int codesRemain, const int currentDepth, int *codesDest, Correction *correction,
+        WordsPriorityQueuePool *queuePool) {
 
     if (currentDepth < MAX_UMLAUT_SEARCH_DEPTH) {
         for (int i = 0; i < codesRemain; ++i) {
@@ -105,8 +111,8 @@
                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
                         codesBuffer, codesBufferSize, flags,
                         codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
-                        currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, outWords,
-                        frequencies);
+                        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 +120,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, codesBufferSize, flags,
+                        codesSrc + i * MAX_PROXIMITY_CHARS, codesRemain - i, currentDepth + 1,
+                        codesDest + i * MAX_PROXIMITY_CHARS, correction, queuePool);
                 return;
             }
         }
@@ -132,40 +138,39 @@
         memcpy(codesDest, codesSrc, remainingBytes);
 
     getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-            (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies,
-            flags);
+            (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + 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) {
 
+    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)];
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
+                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++;
-    }
+    const int suggestedWordsCount =
+            queuePool->getMasterQueue()->outputSuggestions(frequencies, outWords);
 
     if (DEBUG_DICT) {
-        LOGI("Returning %d words", suggestedWordsCount);
+        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]);
+            AKLOGI("%s %i", s, frequencies[j]);
 #endif
         }
     }
@@ -175,23 +180,19 @@
 }
 
 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);
+    queuePool->clearAll();
     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);
@@ -209,12 +210,13 @@
     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) {
+            && inputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
+        for (int i = 1; i < inputLength; ++i) {
             if (DEBUG_DICT) {
-                LOGI("--- Suggest missing space characters %d", i);
+                AKLOGI("--- Suggest missing space characters %d", i);
             }
-            getMissingSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
+            getMissingSpaceWords(proximityInfo, xcoordinates, ycoordinates, codes,
+                    useFullEditDistance, inputLength, i, correction, queuePool);
         }
     }
     PROF_END(5);
@@ -222,172 +224,297 @@
     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) {
+        for (int i = 1; i < inputLength - 1; ++i) {
             if (DEBUG_DICT) {
-                LOGI("--- Suggest words with proximity space %d", i);
+                AKLOGI("--- 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",
+                AKLOGI("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);
+                getMistypedSpaceWords(proximityInfo, xcoordinates, ycoordinates, codes,
+                        useFullEditDistance, inputLength, i, correction, queuePool);
             }
         }
     }
     PROF_END(6);
+    if (DEBUG_DICT) {
+        queuePool->dumpSubQueue1TopSuggestions();
+        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+            WordsPriorityQueue* queue = queuePool->getSubQueue1(i);
+            if (queue->size() > 0) {
+                WordsPriorityQueue::SuggestedWord* sw = queue->top();
+                const int score = sw->mScore;
+                const unsigned short* word = sw->mWord;
+                const int wordLength = sw->mWordLength;
+                double ns = Correction::RankingAlgorithm::calcNormalizedScore(
+                        proximityInfo->getPrimaryInputWord(), i, word, wordLength, score);
+                ns += 0;
+                AKLOGI("--- TOP SUB WORDS for %d --- %d %f [%d]", i, score, ns,
+                        (ns > TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD));
+                DUMP_WORD(proximityInfo->getPrimaryInputWord(), i);
+                DUMP_WORD(word, wordLength);
+            }
+        }
+    }
 }
 
 void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
-        const int *yCoordinates, const int *codes, const int 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);
+}
+
+void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
+        const int inputLength, Correction *correction, WordsPriorityQueuePool *queuePool,
+        const bool doAutoCompletion, const int maxErrors) {
     // 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);
             // 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(
+void UnigramDictionary::getMissingSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates,
+        const int *ycoordinates, const int *codes, const bool useFullEditDistance,
         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);
+        WordsPriorityQueuePool* queuePool) {
+    getSplitTwoWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
+            useFullEditDistance, inputLength, missingSpacePos, -1/* spaceProximityPos */,
+            correction, queuePool);
 }
 
-void UnigramDictionary::getMistypedSpaceWords(
+void UnigramDictionary::getMistypedSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates,
+        const int *ycoordinates, const int *codes, const bool useFullEditDistance,
         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);
+        WordsPriorityQueuePool* queuePool) {
+    getSplitTwoWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
+            useFullEditDistance, inputLength, -1 /* missingSpacePos */, spaceProximityPos,
+            correction, queuePool);
 }
 
-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,
+        const TerminalAttributes& terminalAttributes, Correction *correction,
+        WordsPriorityQueuePool *queuePool, const bool addToMasterQueue) {
+    const int inputIndex = correction->getInputIndex();
+    const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT;
 
-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 (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) {
+        // TODO: Check the validity of "inputIndex == wordLength"
+        //if (addToSubQueue && inputIndex == wordLength) {
+        WordsPriorityQueue *subQueue = queuePool->getSubQueue1(inputIndex);
+        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();
+void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const bool useFullEditDistance, const int inputLength, const int missingSpacePos,
+        const int  spaceProximityPos, Correction *correction, WordsPriorityQueuePool* queuePool) {
+    if (inputLength >= MAX_WORD_LENGTH) return;
+    if (DEBUG_DICT) {
+        int inputCount = 0;
+        if (spaceProximityPos >= 0) ++inputCount;
+        if (missingSpacePos >= 0) ++inputCount;
+        assert(inputCount <= 1);
+    }
+
+    WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
+
+    const bool isSpaceProximity = spaceProximityPos >= 0;
+
+    // First word
+    const int firstInputWordStartPos = 0;
+    const int firstInputWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
+    int firstFreq = getMostFrequentWordLike(
+            firstInputWordStartPos, firstInputWordLength, proximityInfo, mWord);
+    unsigned short* firstOutputWord = 0;
+    int firstOutputWordLength = 0;
+    if (firstFreq > 0) {
+        firstOutputWordLength = firstInputWordLength;
+        firstOutputWord = mWord;
+    } else {
+        if (masterQueue->size() > 0) {
+            double nsForMaster = masterQueue->getHighestNormalizedScore(
+                    proximityInfo->getPrimaryInputWord(), inputLength, 0, 0, 0);
+            if (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD) {
+                // Do nothing if the highest suggestion exceeds the threshold.
+                return;
+            }
+        }
+        WordsPriorityQueue* firstWordQueue = queuePool->getSubQueue1(firstInputWordLength);
+        if (firstWordQueue->size() < 1) {
+            return;
+        }
+        int score = 0;
+        const double ns = firstWordQueue->getHighestNormalizedScore(
+                proximityInfo->getPrimaryInputWord(), firstInputWordLength,
+                &firstOutputWord, &score, &firstOutputWordLength);
+        // 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) {
+            return;
+        }
+        firstFreq = score >> (firstOutputWordLength
+                + TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER);
+    }
+
+    if (DEBUG_DICT) {
+        AKLOGI("First freq: %d", firstFreq);
+    }
+
+    if (firstFreq <= 0 || firstOutputWordLength <= 0 || MAX_WORD_LENGTH <= firstOutputWordLength) {
+        return;
+    }
+
+    // Allocating fixed length array on stack
+    unsigned short outputWord[MAX_WORD_LENGTH];
+    int outputWordLength = 0;
+
+    for (int i = 0; i < firstOutputWordLength; ++i) {
+        outputWord[i] = firstOutputWord[i];
+    }
+
+    outputWord[firstOutputWordLength] = SPACE;
+    outputWordLength = firstOutputWordLength + 1;
+
+    //const int outputWordLength = firstOutputWordLength + secondWordLength + 1;
+    // Space proximity preparation
+    //WordsPriorityQueue *subQueue = queuePool->getSubQueue1();
+    //initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, firstOutputWordLength,
+    //subQueue, correction);
+    //getSuggestionCandidates(useFullEditDistance, firstOutputWordLength, correction, subQueue,
+    //false, MAX_ERRORS_FOR_TWO_WORDS);
+
+    // Second word
+    const int secondInputWordLength = isSpaceProximity
+            ? (inputLength - spaceProximityPos - 1)
+            : (inputLength - missingSpacePos);
+    const int secondInputWordStartPos =
+            isSpaceProximity ? (spaceProximityPos + 1) : missingSpacePos;
+    int secondFreq = getMostFrequentWordLike(
+            secondInputWordStartPos, secondInputWordLength, proximityInfo, mWord);
+    unsigned short* secondOutputWord = 0;
+    int secondOutputWordLength = 0;
+
+    if (secondFreq > 0) {
+        secondOutputWordLength = secondInputWordLength;
+        secondOutputWord = mWord;
+    }
+
+    if (DEBUG_DICT) {
+        AKLOGI("Second freq: %d", secondFreq);
+    }
+
+    if (secondFreq <= 0 || secondOutputWordLength <= 0
+            || MAX_WORD_LENGTH <= (firstOutputWordLength + 1 + secondOutputWordLength)) {
+        return;
+    }
+
+    for (int i = 0; i < secondOutputWordLength; ++i) {
+        outputWord[firstOutputWordLength + 1 + i] = secondOutputWord[i];
+    }
+
+    outputWordLength += secondOutputWordLength;
+
+    // TODO: Remove initSuggestions and correction->setCorrectionParams
+    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
+
+    correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
+            -1 /* transposedPos */, spaceProximityPos, missingSpacePos,
+            useFullEditDistance, false /* doAutoCompletion */, MAX_ERRORS_FOR_TWO_WORDS);
+    const int pairFreq = correction->getFreqForSplitTwoWords(firstFreq, secondFreq, outputWord);
+    if (DEBUG_DICT) {
+        AKLOGI("Split two words:  %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
+    }
+    addWord(outputWord, outputWordLength, pairFreq, masterQueue);
+    return;
+}
+
+void UnigramDictionary::getSplitTwoWordsSuggestionsOld(ProximityInfo *proximityInfo,
+        const int *xcoordinates, const int *ycoordinates, const int *codes,
+        const bool useFullEditDistance, const int inputLength, const int missingSpacePos,
+        const int  spaceProximityPos, Correction *correction, WordsPriorityQueuePool* queuePool) {
+    WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
+
     if (DEBUG_DICT) {
         int inputCount = 0;
         if (spaceProximityPos >= 0) ++inputCount;
@@ -408,11 +535,21 @@
         return;
 
     const int newWordLength = firstWordLength + secondWordLength + 1;
+
+
+    // Space proximity preparation
+    //WordsPriorityQueue *subQueue = queuePool->getSubQueue1();
+    //initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, firstWordLength, subQueue,
+    //correction);
+    //getSuggestionCandidates(useFullEditDistance, firstWordLength, correction, subQueue, false,
+    //MAX_ERRORS_FOR_TWO_WORDS);
+
     // Allocating variable length array on stack
     unsigned short word[newWordLength];
-    const int firstFreq = getMostFrequentWordLike(firstWordStartPos, firstWordLength, mWord);
+    const int firstFreq = getMostFrequentWordLike(
+            firstWordStartPos, firstWordLength, proximityInfo, mWord);
     if (DEBUG_DICT) {
-        LOGI("First freq: %d", firstFreq);
+        AKLOGI("First freq: %d", firstFreq);
     }
     if (firstFreq <= 0) return;
 
@@ -420,9 +557,10 @@
         word[i] = mWord[i];
     }
 
-    const int secondFreq = getMostFrequentWordLike(secondWordStartPos, secondWordLength, mWord);
+    const int secondFreq = getMostFrequentWordLike(
+            secondWordStartPos, secondWordLength, proximityInfo, mWord);
     if (DEBUG_DICT) {
-        LOGI("Second  freq:  %d", secondFreq);
+        AKLOGI("Second  freq:  %d", secondFreq);
     }
     if (secondFreq <= 0) return;
 
@@ -431,22 +569,28 @@
         word[i] = mWord[i - firstWordLength - 1];
     }
 
-    const int pairFreq = mCorrection->getFreqForSplitTwoWords(firstFreq, secondFreq, word);
+    // TODO: Remove initSuggestions and correction->setCorrectionParams
+    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
+
+    correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
+            -1 /* transposedPos */, spaceProximityPos, missingSpacePos,
+            useFullEditDistance, false /* doAutoCompletion */, MAX_ERRORS_FOR_TWO_WORDS);
+    const int pairFreq = correction->getFreqForSplitTwoWords(firstFreq, secondFreq, word);
     if (DEBUG_DICT) {
-        LOGI("Split two words:  %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
+        AKLOGI("Split two words:  %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
     }
-    addWord(word, newWordLength, pairFreq);
+    addWord(word, newWordLength, pairFreq, masterQueue);
     return;
 }
 
 // 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 +614,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 +627,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 +665,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 +742,7 @@
 // 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) {
     if (DEBUG_DICT) {
         correction->checkState();
     }
@@ -648,7 +793,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 +817,13 @@
     } 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);
 
         // 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 +848,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..b950971 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,66 @@
     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);
+        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);
+    void getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo,
+            const int *xcoordinates, const int *ycoordinates, const int *codes,
+            const bool useFullEditDistance, const int inputLength, const int spaceProximityPos,
+            const int missingSpacePos, Correction *correction, WordsPriorityQueuePool* queuePool);
+    void getSplitTwoWordsSuggestionsOld(ProximityInfo *proximityInfo,
+            const int *xcoordinates, const int *ycoordinates, const int *codes,
+            const bool useFullEditDistance, const int inputLength, const int spaceProximityPos,
+            const int missingSpacePos, Correction *correction, WordsPriorityQueuePool* queuePool);
+    void getMissingSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates,
+            const int *ycoordinates, const int *codes, const bool useFullEditDistance,
+            const int inputLength, const int missingSpacePos, Correction *correction,
+            WordsPriorityQueuePool* queuePool);
+    void getMistypedSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates,
+            const int *ycoordinates, const int *codes, const bool useFullEditDistance,
+            const int inputLength, const int spaceProximityPos, Correction *correction,
+            WordsPriorityQueuePool* queuePool);
+    void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
+            Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue);
     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);
     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);
 
     const uint8_t* const DICT_ROOT;
     const int MAX_WORD_LENGTH;
@@ -127,14 +149,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..c85f2b9
--- /dev/null
+++ b/native/src/words_priority_queue.h
@@ -0,0 +1,193 @@
+/*
+ * 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 <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, 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(mSuggestions.top()->mWord, mSuggestions.top()->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..5fa2548
--- /dev/null
+++ b/native/src/words_priority_queue_pool.h
@@ -0,0 +1,86 @@
+/*
+ * 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 < SUB_QUEUE_MAX_COUNT;
+                ++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) {
+            mSubQueues1[i] = new(mSubQueueBuf1 + subQueueBufOffset)
+                    WordsPriorityQueue(subQueueMaxWords, maxWordLength);
+            mSubQueues2[i] = new(mSubQueueBuf2 + subQueueBufOffset)
+                    WordsPriorityQueue(subQueueMaxWords, maxWordLength);
+        }
+    }
+
+    virtual ~WordsPriorityQueuePool() {
+    }
+
+    WordsPriorityQueue* getMasterQueue() {
+        return mMasterQueue;
+    }
+
+    // TODO: Come up with more generic pool
+    WordsPriorityQueue* getSubQueue1(const int id) {
+        if (DEBUG_WORDS_PRIORITY_QUEUE) {
+            assert(id >= 0 && id < SUB_QUEUE_MAX_COUNT);
+        }
+        return mSubQueues1[id];
+    }
+
+    WordsPriorityQueue* getSubQueue2(const int id) {
+        if (DEBUG_WORDS_PRIORITY_QUEUE) {
+            assert(id >= 0 && id < SUB_QUEUE_MAX_COUNT);
+        }
+        return mSubQueues2[id];
+    }
+
+    inline void clearAll() {
+        mMasterQueue->clear();
+        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+            mSubQueues1[i]->clear();
+            mSubQueues2[i]->clear();
+        }
+    }
+
+    void dumpSubQueue1TopSuggestions() {
+        AKLOGI("DUMP SUBQUEUE1 TOP SUGGESTIONS");
+        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+            mSubQueues1[i]->dumpTopWord();
+        }
+    }
+
+ private:
+    WordsPriorityQueue* mMasterQueue;
+    WordsPriorityQueue* mSubQueues1[SUB_QUEUE_MAX_COUNT];
+    WordsPriorityQueue* mSubQueues2[SUB_QUEUE_MAX_COUNT];
+    char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
+    char mSubQueueBuf1[SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
+    char mSubQueueBuf2[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
index 6a5d6d7..453fc9f 100644
--- a/tests/res/raw/test.dict
+++ b/tests/res/raw/test.dict
Binary files differ
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
new file mode 100644
index 0000000..bfd1c17
--- /dev/null
+++ b/tests/res/values/strings.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.
+*/
+-->
+
+<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_escape">"\\\\"</string>
+    <string name="escaped_label">"a\\bc"</string>
+    <string name="escaped_label_at_beginning">"\\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_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>
+</resources>
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java
index 4050a71..29881d9 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java
@@ -16,30 +16,53 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import com.android.inputmethod.keyboard.internal.KeyStyles.EmptyKeyStyle;
-
+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 KeyStylesTests 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 static void assertTextArray(String message, CharSequence value,
-            CharSequence ... expected) {
-        final CharSequence actual[] = EmptyKeyStyle.parseCsvText(value);
+    private void assertTextArray(String message, String value, String ... expected) {
+        final String actual[] = KeyStyles.parseCsvText(value, mTestResources,
+                R.string.empty_string);
         if (expected.length == 0) {
             assertNull(message, actual);
             return;
         }
-        assertSame(message + ": result length", expected.length, actual.length);
+        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.
+        }
+    }
+
     public void testParseCsvTextZero() {
         assertTextArray("Empty string", "");
     }
@@ -52,7 +75,10 @@
         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("Label surrounded by spaces", " abc ", " abc ");
+
+        assertTextArray("Incomplete resource reference 1", "string", "string");
+        assertTextArray("Incomplete resource reference 2", "@strin", "@strin");
     }
 
     public void testParseCsvTextSingleEscaped() {
@@ -60,11 +86,13 @@
         assertTextArray("Escaped comma", "\\,", ",");
         assertTextArray("Escaped escape", "\\\\", "\\");
         assertTextArray("Escaped label", "a\\bc", "abc");
-        assertTextArray("Escaped label at begininng", "\\abc", "abc");
+        assertTextArray("Escaped label at beginning", "\\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");
+
+        assertTextArray("Escaped @string", "\\@string/empty_string", "@string/empty_string");
     }
 
     public void testParseCsvTextMulti() {
@@ -86,5 +114,109 @@
                 "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 1", "@string", "@string");
+        assertError("Incomplete resource name 2", "@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 ");
+    }
+
+    public void testParseCsvResourceSingleEscaped() {
+        assertTextArray("Escaped char",
+                "@string/escaped_char", "a");
+        assertTextArray("Escaped comma",
+                "@string/escaped_comma", ",");
+        assertTextArray("Escaped escape",
+                "@string/escaped_escape", "\\");
+        assertTextArray("Escaped label",
+                "@string/escaped_label", "abc");
+        assertTextArray("Escaped label at beginning",
+                "@string/escaped_label_at_beginning", "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 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", "def", "ghi");
+        assertTextArray("Multiple labels with escape surrounded by spaces",
+                "@string/multiple_labels_with_escape_surrounded_by_spaces",
+                " abc ", " def ", " ghi ");
+        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("Multiple single resource chars and labels",
+                "@string/single_char,@string/single_label,@string/escaped_comma",
+                "a", "abc", ",");
+        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");
     }
 }
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..729120b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
@@ -0,0 +1,162 @@
+/*
+ * 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 {
+    // Shift key chording input.
+    public void testChording() {
+        // Press shift key and hold, enter into choring shift state.
+        pressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+
+        // Press/release letter keys.
+        chordingPressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        chordingPressAndReleaseKey('X', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Release shift key, switch back to alphabet.
+        releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
+
+        // Press symbols key and hold, enter into choring symbols state.
+        pressKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+
+        // Press/release symbol letter keys.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        chordingPressAndReleaseKey('2', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Release symbols key, switch back to alphabet.
+        releaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+    }
+
+    // Shift key chording input in shift locked.
+    public void testShiftChordingShiftLocked() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press shift key and hold, enter into choring shift state.
+        pressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+
+        // Press/release letter keys.
+        chordingPressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        chordingPressAndReleaseKey('X', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Release shift key, switch back to alphabet shift locked.
+        releaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED);
+
+        // Press symbols key and hold, enter into choring symbols state.
+        pressKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+
+        // Press/release symbol letter keys.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        chordingPressAndReleaseKey('2', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Release symbols key, switch back to alphabet shift locked.
+        releaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Symbols key chording input.
+    public void testSymbolsChording() {
+        // Press/release symbols key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press shift key and hold, enter into choring symbols shifted state.
+        pressKey(CODE_SHIFT, SYMBOLS_SHIFTED);
+
+        // Press/release symbols keys.
+        chordingPressAndReleaseKey('1', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        chordingPressAndReleaseKey('2', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+
+        // Release shift 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 keys.
+        chordingPressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        chordingPressAndReleaseKey('b', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Release "ABC" key, switch back to symbols.
+        releaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+    }
+
+    // Symbols shifted key chording input in symbol.
+    public void testSymbolsShiftedChording() {
+        // Press/release symbols key, enter symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release shift key, enter symbols shifted.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+
+        // Press shift key and hold, enter into chording symbols state.
+        pressKey(CODE_SHIFT, SYMBOLS_UNSHIFTED);
+
+        // Press/release symbol letter keys.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        chordingPressAndReleaseKey('2', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Release shift 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 keys.
+        chordingPressAndReleaseKey('a', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        chordingPressAndReleaseKey('b', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Release "ABC" key, switch back to symbols.
+        releaseKey(CODE_SYMBOL, SYMBOLS_SHIFTED);
+    }
+
+    // Chording shift key in automatic upper case.
+    public void testAutomaticUpperCaseChording() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state with auto caps enabled.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press shift key and hold, enter into chording shift state.
+        pressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+
+        // Press/release letter keys.
+        chordingPressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Release shift key, switch back to alphabet.
+        releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
+    }
+
+    // Chording symbol key in automatic upper case.
+    public void testAutomaticUpperCaseChording2() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state with auto caps enabled.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press "123?" key and hold, enter into chording symbols state.
+        pressKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED);
+
+        // Press/release symbol letter keys.
+        chordingPressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Release "123?" key, switch back to alphabet.
+        releaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
+    }
+
+    // TODO: Multitouch test
+
+    // TODO: n-Keys roll over test
+}
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..d13ca63
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -0,0 +1,353 @@
+/*
+ * 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 mode.
+    public void testShift() {
+        // Press/release shift key, enter into shift state.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release shift key, back to normal state.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter into shift state.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release letter key, switch back to normal state.
+        pressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Shift key sliding input.
+    public void testShiftSliding() {
+        // Press and slide from shift key.
+        pressAndSlideFromKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Enter/release letter key, switch back to alphabet.
+        pressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Switching between alphabet and symbols.
+    public void testAlphabetAndSymbols() {
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release "?123" key, back to alphabet.
+        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.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press/release "ABC" key, switch back to shift locked mode.
+        pressAndReleaseKey(CODE_SYMBOL, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+    }
+
+    // Symbols key sliding input.
+    public void testSymbolsSliding() {
+        // Press and slide from "123?" key.
+        pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter/release into symbol key, switch back to alphabet.
+        pressAndReleaseKey('!', SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Switching between symbols and symbols shifted.
+    public void testSymbolsAndSymbolsShifted() {
+        // 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, enter into symbols.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+    }
+
+    // Symbols shift sliding input
+    public void testSymbolsShiftSliding() {
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Press and slide from "=\<" key.
+        pressAndSlideFromKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+
+        // Enter/release symbol shifted letter key, switch back to symbols.
+        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_UNSHIFTED);
+    }
+
+    // Symbols shift sliding input from symbols shifted.
+    public void testSymbolsShiftSliding2() {
+        // 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);
+    }
+
+    // Automatic switch back to alphabet from symbols by space key.
+    public void testSwitchBackBySpace() {
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter a symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter space, switch back to alphabet.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Automatic switch back to shift locked test.
+    public void testSwitchBackBySpaceInShiftLocked() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release "?123" key, enter into symbols.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter a symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter space, switch back to alphabet.
+        pressAndReleaseKey(CODE_SPACE, SYMBOLS_UNSHIFTED, ALPHABET_SHIFT_LOCKED);
+    }
+
+
+    // Automatic switch back to alphabet from symbols 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 a symbol letter.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter switch back letter, switch back to alphabet.
+        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Automatic upper case test
+    public void testAutomaticUpperCase() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state with auto caps enabled.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Sliding from shift key in automatic upper case.
+    public void testAutomaticUpperCaseSliding() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state with auto caps enabled.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press and slide from shift key.
+        pressAndSlideFromKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Enter and release letter key, back to alphabet.
+        pressAndReleaseKey('Z', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Sliding from symbol key in automatic upper case.
+    public void testAutomaticUpperCaseSliding2() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state with auto caps enabled.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press and slide from "123?" key.
+        pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+
+        // Enter and release symbol letter keys, back to alphabet.
+        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Long press shift key.
+    // TODO: Move long press recognizing timer/logic into KeyboardState.
+    public void testLongPressShift() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release letter key, remain in shift locked.
+        pressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release letter key, remain in shift locked.
+        pressAndReleaseKey('B', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release word separator, remain in shift locked.
+        pressAndReleaseKey(CODE_SPACE, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // Long press shift key, back to alphabet.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+     }
+
+    // Leave shift lock with single tap shift key.
+    public void testShiftInShiftLock() {
+        // Long press shift key, enter alphabet shift locked.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Press/release shift key, back to alphabet.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
+
+    // Double tap shift key.
+    // TODO: Move double tap recognizing timer/logic into KeyboardState.
+    public void testDoubleTapShift() {
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // Second shift key tap.
+        // Double tap recognized in LatinKeyboardView.KeyTimerHandler.
+        secondTapShiftKey(ALPHABET_SHIFT_LOCKED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Second shift key tap.
+        // Second tap is ignored in LatinKeyboardView.KeyTimerHandler.
+    }
+
+    // Update shift state.
+    public void testUpdateShiftState() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Update shift state.
+        updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release letter key, back to alphabet.
+        pressAndReleaseKey('A', ALPHABET_AUTOMATIC_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release letter key
+        pressAndReleaseKey('b', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release auto caps trigger letter, back to automatic shifted.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+    }
+
+    // Update shift state when shift locked.
+    public void testUpdateShiftStateInShiftLocked() {
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+
+        // Update shift state when shift locked
+        updateShiftState(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.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+
+        // Press/release symbol key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_UNSHIFTED);
+
+        // Press/release symbol key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release shift 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.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release symbol key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // Press/release symbol key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Press/release shift key.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Change focus to new text field.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+    }
+
+    // Change orientation.
+    public void testChangeOrientation() {
+        // Press/release shift key.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rotate device.
+        rotateDevice(ALPHABET_MANUAL_SHIFTED);
+
+        // Long press shift key, enter alphabet shift locked.
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Rotate device.
+        rotateDevice(ALPHABET_SHIFT_LOCKED);
+
+        // Press/release symbol key.
+        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
+        // Rotate device.
+        rotateDevice(SYMBOLS_UNSHIFTED);
+
+        // Press/release shift key.
+        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
+        // Rotate device.
+        rotateDevice(SYMBOLS_SHIFTED);
+    }
+}
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..62df2cf
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+    }
+
+    public void updateShiftState(int afterUpdate) {
+        mSwitcher.updateShiftState();
+        assertEquals(afterUpdate, mSwitcher.getLayoutId());
+    }
+
+    public void loadKeyboard(int afterLoad) {
+        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+        updateShiftState(afterLoad);
+    }
+
+    public void rotateDevice(int afterRotate) {
+        mSwitcher.saveKeyboardState();
+        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+        assertEquals(afterRotate, mSwitcher.getLayoutId());
+    }
+
+    public void pressKey(int code, int afterPress) {
+        mSwitcher.onPressKey(code);
+        assertEquals(afterPress, mSwitcher.getLayoutId());
+    }
+
+    public void releaseKey(int code, int afterRelease) {
+        mSwitcher.onCodeInput(code, SINGLE);
+        mSwitcher.onReleaseKey(code, NOT_SLIDING);
+        assertEquals(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);
+        assertEquals(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);
+        assertEquals(afterSlide, mSwitcher.getLayoutId());
+    }
+
+    public void longPressShiftKey(int afterPress, int afterLongPress) {
+        // Long press shift key
+        mSwitcher.onPressKey(CODE_SHIFT);
+        assertEquals(afterPress, mSwitcher.getLayoutId());
+        // Long press recognized in LatinKeyboardView.KeyTimerHandler.
+        mSwitcher.onCodeInput(CODE_CAPSLOCK, SINGLE);
+        assertEquals(afterLongPress, mSwitcher.getLayoutId());
+        mSwitcher.onReleaseKey(CODE_SHIFT, NOT_SLIDING);
+        assertEquals(afterLongPress, mSwitcher.getLayoutId());
+    }
+
+    public void secondTapShiftKey(int afterTap) {
+        mSwitcher.onCodeInput(CODE_CAPSLOCK, SINGLE);
+        assertEquals(afterTap, mSwitcher.getLayoutId());
+    }
+}
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..87b4636
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -0,0 +1,133 @@
+/*
+ * 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;
+import com.android.inputmethod.keyboard.internal.KeyboardState.SwitchActions;
+
+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_CAPSLOCK = Keyboard.CODE_CAPSLOCK;
+        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 SYMBOLS_UNSHIFTED = 4;
+        public static final int SYMBOLS_SHIFTED = 5;
+    }
+
+    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 final KeyboardState mState = new KeyboardState(this);
+
+    public int getLayoutId() {
+        return mLayout;
+    }
+
+    public void setAutoCapsMode(boolean autoCaps) {
+        mAutoCapsMode = autoCaps;
+    }
+
+    @Override
+    public void setAlphabetKeyboard() {
+        mLayout = Constants.ALPHABET_UNSHIFTED;
+    }
+
+    @Override
+    public void setShifted(int shiftMode) {
+        if (shiftMode == SwitchActions.UNSHIFT) {
+            mLayout = Constants.ALPHABET_UNSHIFTED;
+        } else if (shiftMode == SwitchActions.MANUAL_SHIFT) {
+            mLayout = Constants.ALPHABET_MANUAL_SHIFTED;
+        } else if (shiftMode == SwitchActions.AUTOMATIC_SHIFT) {
+            mLayout = Constants.ALPHABET_AUTOMATIC_SHIFTED;
+        }
+    }
+
+    @Override
+    public void setShiftLocked(boolean shiftLocked) {
+        if (shiftLocked) {
+            mLayout = Constants.ALPHABET_SHIFT_LOCKED;
+        } else {
+            mLayout = Constants.ALPHABET_UNSHIFTED;
+        }
+    }
+
+    @Override
+    public void setSymbolsKeyboard() {
+        mLayout = Constants.SYMBOLS_UNSHIFTED;
+    }
+
+    @Override
+    public void setSymbolsShiftedKeyboard() {
+        mLayout = Constants.SYMBOLS_SHIFTED;
+    }
+
+    @Override
+    public void requestUpdatingShiftState() {
+        mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
+    }
+
+    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);
+    }
+
+    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
index 798fca0..74aaf9a 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecParserTests.java
@@ -25,11 +25,11 @@
 public class MoreKeySpecParserTests extends AndroidTestCase {
     private Resources mRes;
 
-    private static final int ICON_SETTINGS_KEY = 5;
+    private static final int ICON_SETTINGS_KEY = R.styleable.Keyboard_iconSettingsKey;
     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 ICON_SETTINGS = "@icon/settingsKey";
     private static final String CODE_NON_EXISTING = "@integer/non_existing";
     private static final String ICON_NON_EXISTING = "@icon/non_existing";
 
@@ -53,7 +53,7 @@
         String actualOutputText = MoreKeySpecParser.getOutputText(moreKeySpec);
         assertEquals(message + ": ouptputText:", expectedOutputText, actualOutputText);
 
-        int actualIcon = MoreKeySpecParser.getIconId(moreKeySpec);
+        int actualIcon = MoreKeySpecParser.getIconAttrId(moreKeySpec);
         assertEquals(message + ": icon:", expectedIcon, actualIcon);
 
         int actualCode = MoreKeySpecParser.getCode(mRes, moreKeySpec);
@@ -66,7 +66,7 @@
             assertParser(message, moreKeySpec, expectedLabel, expectedOutputText, expectedIcon,
                     expectedCode);
             fail(message);
-        } catch (MoreKeySpecParser.MoreKeySpecParserError pcpe) {
+        } catch (Exception pcpe) {
             // success.
         }
     }
@@ -89,78 +89,78 @@
         assertParser("Single escaped at", "\\@",
                 "@", null, ICON_UNDEFINED, '@');
         assertParser("Single letter with outputText", "a|abc",
-                "a", "abc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a", "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Single letter with escaped outputText", "a|a\\|c",
-                "a", "a|c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a", "a|c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Single letter with comma outputText", "a|a,b",
-                "a", "a,b", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "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_DUMMY);
+                "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_DUMMY);
+                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Single letter with outputText contains at", "a|a@c",
-                "a", "a@c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a", "a@c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Single letter with escaped at outputText", "a|\\@bc",
-                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Single escaped escape with outputText", "\\\\|\\\\",
-                "\\", "\\", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "\\", "\\", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Single escaped bar with outputText", "\\||\\|",
-                "|", "|", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "|", "|", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         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);
+                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with escaped bar", "a\\|c",
-                "a|c", "a|c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a|c", "a|c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with escaped escape", "a\\\\c",
-                "a\\c", "a\\c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a\\c", "a\\c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with comma", "a,c",
-                "a,c", "a,c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "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_DUMMY);
+                "a,c", "a,c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label starts with at", "@bc",
-                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label contains at", "a@c",
-                "a@c", "a@c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a@c", "a@c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with escaped at", "\\@bc",
-                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "@bc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with escaped letter", "\\abc",
-                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "abc", "abc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with outputText", "abc|def",
-                "abc", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "abc", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with comma and outputText", "a,c|def",
-                "a,c", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "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_DUMMY);
+                "a,c", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Escaped label with outputText", "a\\|c|def",
-                "a|c", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "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_DUMMY);
+                "abc", "d|f", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Escaped escape label with outputText", "a\\\\|def",
-                "a\\", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "a\\", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label starts with at and outputText", "@bc|def",
-                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "@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_DUMMY);
+                "a@c", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Escaped at label with outputText", "\\@bc|def",
-                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "@bc", "def", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with comma outputText", "abc|a,b",
-                "abc", "a,b", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "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_DUMMY);
+                "abc", "a,b", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with outputText starts with at", "abc|@bc",
-                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with outputText contains at", "abc|a@c",
-                "abc", "a@c", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "abc", "a@c", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with escaped at outputText", "abc|\\@bc",
-                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "abc", "@bc", ICON_UNDEFINED, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Label with escaped bar outputText", "abc|d\\|f",
-                "abc", "d|f", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                "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_DUMMY);
+                "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,
@@ -169,13 +169,13 @@
 
     public void testIconAndCode() {
         assertParser("Icon with outputText", ICON_SETTINGS + "|abc",
-                null, "abc", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
+                null, "abc", ICON_SETTINGS_KEY, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Icon with outputText starts with at", ICON_SETTINGS + "|@bc",
-                null, "@bc", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
+                null, "@bc", ICON_SETTINGS_KEY, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Icon with outputText contains at", ICON_SETTINGS + "|a@c",
-                null, "a@c", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
+                null, "a@c", ICON_SETTINGS_KEY, Keyboard.CODE_OUTPUT_TEXT);
         assertParser("Icon with escaped at outputText", ICON_SETTINGS + "|\\@bc",
-                null, "@bc", ICON_SETTINGS_KEY, Keyboard.CODE_DUMMY);
+                null, "@bc", ICON_SETTINGS_KEY, 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,
@@ -190,7 +190,7 @@
         assertParserError("Empty spec", "", null,
                 null, ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED);
         assertParserError("Empty label with outputText", "|a",
-                null, "a", ICON_UNDEFINED, Keyboard.CODE_DUMMY);
+                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|",
@@ -200,9 +200,9 @@
         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);
+                null, null, ICON_SETTINGS_KEY, 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|",
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..06ee5bf
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -0,0 +1,122 @@
+/*
+ * 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.preference.PreferenceManager;
+import android.test.ServiceTestCase;
+import android.text.InputType;
+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;
+
+public class InputLogicTests extends ServiceTestCase<LatinIME> {
+
+    private static final String PREF_DEBUG_MODE = "debug_mode";
+
+    private LatinIME mLatinIME;
+    private TextView mTextView;
+
+    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();
+        final InputConnection ic = mTextView.onCreateInputConnection(ei);
+        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);
+    }
+
+    // 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, new int[] { codePoint },
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+        //mLatinIME.onReleaseKey(codePoint, false);
+    }
+
+    private void type(final String stringToType) {
+        for (int i = 0; i < stringToType.length(); ++i) {
+            type(stringToType.codePointAt(i));
+        }
+    }
+
+    public void testTypeWord() {
+        final String wordToType = "abcd";
+        type(wordToType);
+        assertEquals("type word", wordToType, mTextView.getText().toString());
+    }
+
+    public void testPickSuggestionThenBackspace() {
+        final String wordToType = "tgis";
+        type(wordToType);
+        mLatinIME.pickSuggestionManually(0, wordToType);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("press suggestion then backspace", wordToType, mTextView.getText().toString());
+    }
+
+}
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index fec3e8e..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
index 464930f..2e36245 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -19,72 +19,55 @@
 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 com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSet;
 
 import java.io.File;
 import java.util.Locale;
 
 public class SuggestHelper {
     protected final Suggest mSuggest;
-    protected final LatinKeyboard mKeyboard;
+    protected int mCorrectionMode;
+    protected final Keyboard mKeyboard;
     private final KeyDetector mKeyDetector;
 
-    public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
+    public SuggestHelper(Context context, int dictionaryId, KeyboardSet keyboardSet) {
         // 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();
+        mKeyboard = keyboardSet.getMainKeyboard();
         mKeyDetector = new KeyDetector(0);
         init();
     }
 
     protected SuggestHelper(final Context context, final File dictionaryPath,
-            final long startOffset, final long length, final KeyboardId keyboardId,
+            final long startOffset, final long length, final KeyboardSet keyboardSet,
             final Locale locale) {
         mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null, locale);
-        mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build();
+        mKeyboard = keyboardSet.getMainKeyboard();
         mKeyDetector = new KeyDetector(0);
         init();
     }
 
     private void init() {
-        mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
+        setCorrectionMode(Suggest.CORRECTION_FULL);
         mKeyDetector.setKeyboard(mKeyboard, 0, 0);
         mKeyDetector.setProximityCorrectionEnabled(true);
         mKeyDetector.setProximityThreshold(mKeyboard.mMostCommonKeyWidth);
     }
 
     public void setCorrectionMode(int correctionMode) {
-        mSuggest.setCorrectionMode(correctionMode);
+        mCorrectionMode = 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);
-        }
+        word.setComposingWord(s, mKeyboard, mKeyDetector);
         return word;
     }
 
@@ -96,13 +79,13 @@
     // 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());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
     }
 
     public CharSequence getFirstSuggestion(CharSequence typed) {
         WordComposer word = createWordComposer(typed);
         SuggestedWords suggestions = mSuggest.getSuggestions(word, null,
-                mKeyboard.getProximityInfo());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
         // Note that suggestions.getWord(0) is the word user typed.
         return suggestions.size() > 1 ? suggestions.getWord(1) : null;
     }
@@ -110,7 +93,7 @@
     public CharSequence getAutoCorrection(CharSequence typed) {
         WordComposer word = createWordComposer(typed);
         SuggestedWords suggestions = mSuggest.getSuggestions(word, null,
-                mKeyboard.getProximityInfo());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
         // Note that suggestions.getWord(0) is the word user typed.
         return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
                 ? suggestions.getWord(1) : null;
@@ -119,7 +102,7 @@
     public int getSuggestIndex(CharSequence typed, CharSequence expected) {
         WordComposer word = createWordComposer(typed);
         SuggestedWords suggestions = mSuggest.getSuggestions(word, null,
-                mKeyboard.getProximityInfo());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
         // 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))
@@ -131,7 +114,8 @@
     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());
+            mSuggest.getSuggestions(firstChar, previous, mKeyboard.getProximityInfo(),
+                    mCorrectionMode);
         }
     }
 
@@ -139,7 +123,7 @@
         WordComposer word = createWordComposer(typed);
         getBigramSuggestions(previous, typed);
         SuggestedWords suggestions = mSuggest.getSuggestions(word, previous,
-                mKeyboard.getProximityInfo());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
         return suggestions.size() > 1 ? suggestions.getWord(1) : null;
     }
 
@@ -147,7 +131,7 @@
         WordComposer word = createWordComposer(typed);
         getBigramSuggestions(previous, typed);
         SuggestedWords suggestions = mSuggest.getSuggestions(word, previous,
-                mKeyboard.getProximityInfo());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
         return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
                 ? suggestions.getWord(1) : null;
     }
@@ -157,7 +141,7 @@
         WordComposer word = createWordComposer(typed);
         getBigramSuggestions(previous, typed);
         SuggestedWords suggestions = mSuggest.getSuggestions(word, previous,
-                mKeyboard.getProximityInfo());
+                mKeyboard.getProximityInfo(), mCorrectionMode);
         for (int i = 1; i < suggestions.size(); i++) {
             if (TextUtils.equals(suggestions.getWord(i), expected))
                 return i;
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTests.java b/tests/src/com/android/inputmethod/latin/SuggestTests.java
index 4080f34..e12ae58 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTests.java
@@ -33,7 +33,7 @@
         final Locale locale = Locale.US;
         mHelper = new SuggestHelper(
                 getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
-                createKeyboardId(locale, Configuration.ORIENTATION_PORTRAIT), locale);
+                createKeyboardSet(locale, Configuration.ORIENTATION_PORTRAIT), locale);
         mHelper.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
     }
 
@@ -183,7 +183,8 @@
                 "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"));
+        // TODO: The following test fails due to "transpose correction".
+        // 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
index 058a3e7..73e34ba 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
@@ -19,11 +19,12 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Configuration;
 import android.test.AndroidTestCase;
+import android.text.InputType;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardSet;
 
 import java.io.File;
 import java.io.InputStream;
@@ -38,7 +39,12 @@
         mTestPackageFile = new File(getTestContext().getApplicationInfo().sourceDir);
     }
 
-    protected KeyboardId createKeyboardId(Locale locale, int orientation) {
+    protected KeyboardSet createKeyboardSet(Locale locale, int orientation) {
+        return createKeyboardSet(locale, orientation, false);
+    }
+
+    protected KeyboardSet createKeyboardSet(Locale locale, int orientation,
+            boolean touchPositionCorrectionEnabled) {
         final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
         final int width;
         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -50,10 +56,12 @@
                     + "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);
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
+        final KeyboardSet.Builder builder = new KeyboardSet.Builder(getContext(), editorInfo);
+        builder.setScreenGeometry(orientation, width);
+        builder.setSubtype(locale, true, touchPositionCorrectionEnabled);
+        return builder.build();
     }
 
     protected InputStream openTestRawResource(int resIdInTest) {
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
index 023e20a..74fadf7 100644
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
@@ -16,11 +16,11 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.keyboard.KeyboardId;
-
 import android.content.Context;
 import android.text.TextUtils;
 
+import com.android.inputmethod.keyboard.KeyboardSet;
+
 import java.io.File;
 import java.util.Locale;
 import java.util.StringTokenizer;
@@ -31,14 +31,14 @@
 
     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);
+            final int userBigramDelete, final KeyboardSet keyboardSet, final Locale locale) {
+        super(context, dictionaryPath, startOffset, length, keyboardSet, 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);
+        setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
         mSuggest.setUserBigramDictionary(mUserBigram);
     }
 
@@ -59,7 +59,8 @@
         flushUserBigrams();
         if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
             WordComposer firstChar = createWordComposer(Character.toString(typed));
-            mSuggest.getSuggestions(firstChar, previous, mKeyboard.getProximityInfo());
+            mSuggest.getSuggestions(firstChar, previous, mKeyboard.getProximityInfo(),
+                    mCorrectionMode);
             boolean reloading = mUserBigram.reloadDictionaryIfRequired();
             if (reloading) mUserBigram.waitForDictionaryLoading();
             mUserBigram.getBigrams(firstChar, previous, mSuggest);
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
index 2bc0aab..2b88a7c 100644
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
@@ -23,7 +23,7 @@
 import java.util.Locale;
 
 public class UserBigramSuggestTests extends SuggestTestsBase {
-    private static final int SUGGESTION_STARTS = 6;
+    private static final int SUGGESTION_STARTS = 1;
     private static final int MAX_DATA = 20;
     private static final int DELETE_DATA = 10;
 
@@ -37,7 +37,7 @@
         mHelper = new UserBigramSuggestHelper(
                 getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
                 MAX_DATA, DELETE_DATA,
-                createKeyboardId(locale, Configuration.ORIENTATION_PORTRAIT), locale);
+                createKeyboardSet(locale, Configuration.ORIENTATION_PORTRAIT), locale);
     }
 
     /************************** Tests ************************/
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..7aadc67 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
      *
@@ -126,7 +130,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 +144,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 +154,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 +265,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 +300,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 +318,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 +335,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 +417,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 +426,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 +460,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 +594,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 +655,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 +694,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;
                 }
             }
 
@@ -814,14 +900,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 +955,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 +992,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 +1049,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 +1079,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;
         }
@@ -996,7 +1134,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);
             }
         }
 
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..2fcd575 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
@@ -39,11 +39,13 @@
         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_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 mOutputXml;
@@ -72,8 +74,9 @@
 
         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>] [-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"
@@ -90,6 +93,7 @@
             }
             String inputBinary = null;
             String inputUnigramXml = null;
+            String inputShortcutXml = null;
             String inputBigramXml = null;
             String outputBinary = null;
             String outputXml = null;
@@ -105,7 +109,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 +120,16 @@
                             } 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_XML.equals(arg)) {
                             outputXml = filename;
+                        } else {
+                            throw new IllegalArgumentException("Unknown option : " + arg);
                         }
                     }
                 } else {
@@ -133,13 +142,14 @@
                     } 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;
             mOutputXml = outputXml;
@@ -167,7 +177,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 +202,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 +211,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);
     }
 
     /**
diff --git a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
index f6220ee..918b1ca 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;
         }
@@ -157,6 +171,24 @@
     }
 
     /**
+     * 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/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()) {