am 23c45bac: Reconcile with ics-mr1-release

* commit '23c45bac209c18dd8fd6260e338e566df946c968':
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index b052532..888d73f 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -5,7 +5,6 @@
     <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"
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..d6b3099
--- /dev/null
+++ b/java/res/values-et/donottranslate-more-keys.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <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>
+</resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 7b89edd..ef9303e 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -35,8 +35,8 @@
     <string name="misc_category" msgid="6894192814868233453">"Autres options"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Paramètres avancés"</string>
     <string name="advanced_settings_summary" msgid="5193513161106637254">"Options destinées aux utilisateurs expérimentés"</string>
-    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Masquer touche agrandie"</string>
-    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sans délai"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Délai masq. touche pop-up"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Aucun délai"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Par défaut"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Proposer noms de contacts"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utiliser des noms de contacts pour les suggestions et corrections"</string>
@@ -51,7 +51,7 @@
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Afficher en mode Portrait"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Toujours masquer"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Afficher touche param."</string>
-    <string name="auto_correction" msgid="4979925752001319458">"Correction automatique"</string>
+    <string name="auto_correction" msgid="4979925752001319458">"Correction auto."</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corriger autom. orthographe (pression sur barre espace/signes ponctuation)"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Simple"</string>
@@ -132,7 +132,7 @@
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Sur clavier principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Sur clavier symboles"</string>
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Désactiver"</string>
-    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micro sur le clavier principal"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micro clavier principal"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micro sur clavier symboles"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Saisie vocale désactivée"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Sélectionner un mode de saisie."</string>
@@ -148,6 +148,6 @@
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglais (Royaume-Uni)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode d\'étude de l\'utilisabilité"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Durée de vibration à chaque pression"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volume sonore à chaque pression"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Paramètres de durée du vibreur à chaque pression de touche"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Paramètres de volume sonore à chaque pression de touche"</string>
 </resources>
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-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/attrs.xml b/java/res/values/attrs.xml
index 0c9ca4f..15e0065 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -185,6 +185,8 @@
     <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 +198,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" />
@@ -247,8 +254,6 @@
         </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" />
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 3f676ab..bad4bc6 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -81,8 +81,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/donottranslate.xml b/java/res/values/donottranslate.xml
index aefaec9..1f7736d 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -166,4 +166,5 @@
     <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/strings.xml b/java/res/values/strings.xml
index e00547a..8f99921 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -31,11 +31,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>
diff --git a/java/res/xml-sw600dp-land/kbd_number.xml b/java/res/xml-sw600dp-land/kbd_number.xml
new file mode 100644
index 0000000..7e3188b
--- /dev/null
+++ b/java/res/xml-sw600dp-land/kbd_number.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.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_rows_number" />
+</Keyboard>
diff --git a/java/res/xml-sw600dp-land/kbd_phone.xml b/java/res/xml-sw600dp-land/kbd_phone.xml
new file mode 100644
index 0000000..28df7ef
--- /dev/null
+++ b/java/res/xml-sw600dp-land/kbd_phone.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.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_rows_phone" />
+</Keyboard>
diff --git a/java/res/xml-sw600dp-land/kbd_phone_shift.xml b/java/res/xml-sw600dp-land/kbd_phone_shift.xml
new file mode 100644
index 0000000..daf1d18
--- /dev/null
+++ b/java/res/xml-sw600dp-land/kbd_phone_shift.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.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="15.00%p"
+>
+    <include
+        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..aba1a80 100644
--- a/java/res/xml-sw600dp/kbd_key_styles.xml
+++ b/java/res/xml-sw600dp/kbd_key_styles.xml
@@ -33,7 +33,7 @@
         <default>
             <key-style
                 latin:styleName="f2PopupStyle"
-                latin:keyLabelOption="hasPopupHint"
+                latin:keyLabelFlags="hasPopupHint"
                 latin:moreKeys="\@icon/3|\@integer/key_settings"
                 latin:backgroundType="functional" />
         </default>
@@ -44,40 +44,48 @@
         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:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:parentStyle="f2PopupStyle" />
     <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"
@@ -89,26 +97,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_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_russian.xml b/java/res/xml-sw600dp/kbd_rows_russian.xml
index cc9ad3a..3395065 100644
--- a/java/res/xml-sw600dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw600dp/kbd_rows_russian.xml
@@ -122,12 +122,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-land/kbd_number.xml b/java/res/xml-sw768dp-land/kbd_number.xml
new file mode 100644
index 0000000..3106dc3
--- /dev/null
+++ b/java/res/xml-sw768dp-land/kbd_number.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.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_rows_number" />
+</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_phone.xml b/java/res/xml-sw768dp-land/kbd_phone.xml
new file mode 100644
index 0000000..7c7af57
--- /dev/null
+++ b/java/res/xml-sw768dp-land/kbd_phone.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.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_rows_phone" />
+</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_phone_shift.xml b/java/res/xml-sw768dp-land/kbd_phone_shift.xml
new file mode 100644
index 0000000..04b018c
--- /dev/null
+++ b/java/res/xml-sw768dp-land/kbd_phone_shift.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.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHorizontalEdgesPadding="10%p"
+    latin:keyWidth="13.250%p"
+>
+    <include
+        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..e6ec53d 100644
--- a/java/res/xml-sw768dp/kbd_key_styles.xml
+++ b/java/res/xml-sw768dp/kbd_key_styles.xml
@@ -26,75 +26,87 @@
         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:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
+        latin:backgroundType="functional" />
+    <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"
         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_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_russian.xml b/java/res/xml-sw768dp/kbd_rows_russian.xml
index e5f5569..eb0baf9 100644
--- a/java/res/xml-sw768dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw768dp/kbd_rows_russian.xml
@@ -28,7 +28,7 @@
     >
         <Key
             latin:keyStyle="tabKeyStyle"
-            latin:keyLabelOption="alignLeft" />
+            latin:keyLabelFlags="alignLeft" />
         <Key
             latin:keyLabel="й" />
         <Key
@@ -63,7 +63,7 @@
     >
         <Key
             latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelOption="alignLeft"
+            latin:keyLabelFlags="alignLeft"
             latin:keyWidth="9.375%p" />
         <Key
             latin:keyLabel="ф" />
diff --git a/java/res/xml-sw768dp/kbd_rows_scandinavian.xml b/java/res/xml-sw768dp/kbd_rows_scandinavian.xml
index b9d1680..c2dead2 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"
@@ -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_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/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 453b05d..5714e09 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>
@@ -67,13 +67,14 @@
         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 +85,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 +97,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 +108,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 +119,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 +130,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 +141,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 +150,28 @@
                 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: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..c2ff4d5 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="iconSpaceKey"
+        latin:parentStyle="numKeyBaseStyle" />
 </merge>
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_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/method.xml b/java/res/xml/method.xml
index 6184add..2b2c00c 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -20,7 +20,7 @@
 <!-- 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, cs, da, de, de(QWERTY), es, es_US, et, fi, fr, fr_CA, fr_CH, hr, hu, it, iw, lt, lv, nb, nl, pl, pt, ru, sr, sv, tr -->
 <!-- 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.-->
@@ -77,6 +77,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 +132,18 @@
     />
     <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"
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..1836f27 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -120,8 +120,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 +137,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 +171,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/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index cef8226..4cb2f20 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -29,7 +29,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,7 +41,7 @@
     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);
@@ -81,7 +80,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 +129,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(tracker, mLastHoverKey, false);
+                mLastHoverKey = key;
+                fireKeyHoverEvent(tracker, mLastHoverKey, true);
             }
 
             return true;
@@ -144,7 +143,7 @@
         return false;
     }
 
-    private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
+    private void fireKeyHoverEvent(PointerTracker tracker, Key key, boolean entering) {
         if (mListener == null) {
             Log.e(TAG, "No accessible keyboard action listener set!");
             return;
@@ -155,11 +154,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/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 7302830..e01262c 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -136,16 +136,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_DUMMY) {
             return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
         }
 
@@ -187,11 +185,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/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/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..b2b68f0 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -46,27 +46,28 @@
      * 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;
 
     /** Icon to display instead of a label. Icon takes precedence over a label */
     private Drawable mIcon;
@@ -105,8 +106,10 @@
     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;
 
     /** The current pressed state of this key */
     private boolean mPressed;
@@ -181,14 +184,15 @@
         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_DUMMY;
         mIcon = icon;
         // Horizontal gap is divided equally to both sides of the key.
         mX = x + mHorizontalGap / 2;
@@ -223,7 +227,7 @@
             if (style == null)
                 throw new ParseException("Unknown key style: " + styleName, parser);
         } else {
-            style = keyStyles.getEmptyKeyStyle();
+            style = KeyStyles.getEmptyKeyStyle();
         }
 
         final float keyXPos = row.getKeyX(keyAttr);
@@ -254,8 +258,7 @@
 
         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,
@@ -275,7 +278,7 @@
         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.
@@ -289,6 +292,8 @@
         } else {
             mCode = Keyboard.CODE_DUMMY;
         }
+        mAltCode = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_DUMMY);
 
         keyAttr.recycle();
     }
@@ -317,11 +322,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 +355,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,19 +368,19 @@
     }
 
     public boolean isAlignLeft() {
-        return (mLabelOption & LABEL_OPTION_ALIGN_LEFT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
     }
 
     public boolean isAlignRight() {
-        return (mLabelOption & LABEL_OPTION_ALIGN_RIGHT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
     }
 
     public boolean isAlignLeftOfCenter() {
-        return (mLabelOption & LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0;
+        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
     }
 
     public boolean hasPopupHint() {
-        return (mLabelOption & LABEL_OPTION_HAS_POPUP_HINT) != 0;
+        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
     }
 
     public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) {
@@ -367,23 +392,23 @@
     }
 
     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() {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 3298c41..2a6e0a2 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -26,7 +26,7 @@
     private static final boolean DEBUG = false;
 
     public static final int NOT_A_CODE = -1;
-    public static final int NOT_A_KEY = -1;
+    private static final int NOT_A_KEY = -1;
 
     private final int mKeyHysteresisDistanceSquared;
 
@@ -96,22 +96,22 @@
     }
 
     /**
-     * Computes maximum size of the array that can contain all nearby key indices returned by
-     * {@link #getKeyIndexAndNearbyCodes}.
+     * Computes maximum size of the array that can contain all nearby key codes returned by
+     * {@link #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() {
@@ -180,31 +180,32 @@
     }
 
     /**
-     * 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) {
+    public Key getKeyAndNearbyCodes(int x, int y, final int[] allCodes) {
         final List<Key> keys = getKeyboard().mKeys;
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
         initializeNearbyKeys();
-        int primaryIndex = NOT_A_KEY;
+        Key primaryKey = null;
         for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
             final Key key = keys.get(index);
             final boolean isOnKey = key.isOnKey(touchX, touchY);
             final int distance = key.squaredDistanceToEdge(touchX, touchY);
             if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
                 final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
-                if (insertedPosition == 0 && isOnKey)
-                    primaryIndex = index;
+                if (insertedPosition == 0 && isOnKey) {
+                    primaryKey = key;
+                }
             }
         }
 
@@ -212,12 +213,31 @@
             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 ? printableCode(key.mCode) : "none";
+    }
+
+    public static String printableCode(int primaryCode) {
+        if (primaryCode < 0) return String.format("%4d", primaryCode);
+        if (primaryCode < 0x100) return String.format("\\u%02x", primaryCode);
+        return String.format("\\u04x", primaryCode);
+    }
+
+    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..2bc140e 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -24,6 +24,7 @@
 import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -65,7 +66,6 @@
     public static final int CODE_DIGIT0 = '0';
     public static final int CODE_PLUS = '+';
 
-
     /** Special keys code.  These should be aligned with values/keycodes.xml */
     public static final int CODE_DUMMY = 0;
     public static final int CODE_SHIFT = -1;
@@ -75,7 +75,6 @@
     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;
 
@@ -112,10 +111,13 @@
     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;
 
+    // TODO: Remove this variable.
+    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
+
     public Keyboard(KeyboardParams params) {
         mId = params.mId;
         mThemeId = params.mThemeId;
@@ -146,82 +148,69 @@
         return mProximityInfo;
     }
 
+    public Key getKey(int code) {
+        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.
     public boolean hasShiftLockKey() {
         return !mShiftLockKeys.isEmpty();
     }
 
-    public boolean setShiftLocked(boolean newShiftLockState) {
+    // TODO: Remove this method.
+    public 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));
-            }
+            key.setIcon(newShiftLockState ? mShiftedIcons.get(key) : mUnshiftedIcons.get(key));
         }
         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));
+    // TODO: Remove this method.
+    public void setShifted(boolean newShiftState) {
+        if (!mShiftState.isShiftLocked()) {
+            for (final Key key : mShiftKeys) {
+                key.setIcon(newShiftState ? mShiftedIcons.get(key) : mUnshiftedIcons.get(key));
             }
         }
-        return mShiftState.setShifted(newShiftState);
+        mShiftState.setShifted(newShiftState);
     }
 
+    // TODO: Move this method to KeyboardId.
     public boolean isShiftedOrShiftLocked() {
         return mShiftState.isShiftedOrShiftLocked();
     }
 
+    // TODO: Remove this method
     public void setAutomaticTemporaryUpperCase() {
-        setShifted(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))) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 2e4988f..ecc821a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.text.TextUtils;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
@@ -58,15 +59,15 @@
     public final int mImeAction;
 
     public final String mXmlName;
-    public final EditorInfo mAttribute;
+    public final EditorInfo mEditorInfo;
 
     private final int mHashCode;
 
     public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int width,
-            int mode, EditorInfo attribute, boolean hasSettingsKey, int f2KeyMode,
+            int mode, EditorInfo editorInfo, 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;
+        final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
+        final int imeOptions = (editorInfo != null) ? editorInfo.imeOptions : 0;
         this.mLocale = locale;
         this.mOrientation = orientation;
         this.mWidth = width;
@@ -89,7 +90,7 @@
                 EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
 
         this.mXmlName = xmlName;
-        this.mAttribute = attribute;
+        this.mEditorInfo = editorInfo;
 
         this.mHashCode = Arrays.hashCode(new Object[] {
                 locale,
@@ -109,7 +110,7 @@
     }
 
     public KeyboardId cloneWithNewXml(String xmlName, int xmlId) {
-        return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mAttribute,
+        return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mEditorInfo,
                 false, F2KEY_MODE_NONE, false, false, false);
     }
 
@@ -133,10 +134,6 @@
         return mXmlId == R.xml.kbd_phone_shift;
     }
 
-    public boolean isNumberKeyboard() {
-        return mMode == MODE_NUMBER;
-    }
-
     @Override
     public boolean equals(Object other) {
         return other instanceof KeyboardId && equals((KeyboardId) other);
@@ -181,6 +178,14 @@
         );
     }
 
+    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 modeName(int mode) {
         switch (mode) {
         case MODE_TEXT: return "text";
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ac718fc..77218d3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -20,7 +20,6 @@
 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;
@@ -30,14 +29,14 @@
 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.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;
 
@@ -45,10 +44,10 @@
 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 = {
@@ -69,9 +68,7 @@
     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;
@@ -81,80 +78,15 @@
     private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
             new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
 
-    private KeyboardLayoutState mSavedKeyboardState = new KeyboardLayoutState();
-
     /** 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;
     }
@@ -173,6 +105,7 @@
         mResources = ims.getResources();
         mPrefs = prefs;
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+        mState = new KeyboardState(this);
         setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
         prefs.registerOnSharedPreferenceChangeListener(this);
     }
@@ -199,14 +132,13 @@
         }
     }
 
-    public void loadKeyboard(EditorInfo editorInfo, Settings.Values settingsValues) {
+    public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
         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),
+                    hasDistinctMultitouch());
         } catch (RuntimeException e) {
             Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
             LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
@@ -214,7 +146,9 @@
     }
 
     public void saveKeyboardState() {
-        mSavedKeyboardState.save();
+        if (mCurrentId != null) {
+            mState.onSaveKeyboardState();
+        }
     }
 
     public void onFinishInputView() {
@@ -230,21 +164,16 @@
         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));
         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.
@@ -297,7 +226,7 @@
     }
 
     private KeyboardId getKeyboardId(EditorInfo editorInfo, final boolean isSymbols,
-            final boolean isShift, Settings.Values settingsValues) {
+            final boolean isShift, SettingsValues settingsValues) {
         final int mode = Utils.getKeyboardMode(editorInfo);
         final int xmlId;
         switch (mode) {
@@ -345,18 +274,25 @@
                 voiceKeyEnabled, hasShortcutKey);
     }
 
-    public int getKeyboardMode() {
-        return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT;
-    }
-
     public boolean isAlphabetMode() {
-        return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
+        final Keyboard keyboard = getLatinKeyboard();
+        return keyboard != null && keyboard.mId.isAlphabetKeyboard();
     }
 
     public boolean isInputViewShown() {
         return mCurrentInputView != null && mCurrentInputView.isShown();
     }
 
+    public boolean isShiftedOrShiftLocked() {
+        final Keyboard keyboard = getLatinKeyboard();
+        return keyboard != null && keyboard.isShiftedOrShiftLocked();
+    }
+
+    public boolean isManualTemporaryUpperCase() {
+        final Keyboard keyboard = getLatinKeyboard();
+        return keyboard != null && keyboard.isManualTemporaryUpperCase();
+    }
+
     public boolean isKeyboardAvailable() {
         if (mKeyboardView != null)
             return mKeyboardView.getKeyboard() != null;
@@ -372,68 +308,43 @@
         return null;
     }
 
-    public boolean isShiftedOrShiftLocked() {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setShifted(int shiftMode) {
+        mInputMethodService.mHandler.cancelUpdateShiftState();
         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) {
+        if (latinKeyboard == null)
+            return;
+        if (shiftMode == AUTOMATIC_SHIFT) {
+            latinKeyboard.setAutomaticTemporaryUpperCase();
+        } else {
+            final boolean shifted = (shiftMode == MANUAL_SHIFT);
             // 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()) {
+            // 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 && mState.isShiftLocked()) {
                 latinKeyboard.setShiftLocked(false);
             }
-            if (latinKeyboard.setShifted(shifted)) {
-                mKeyboardView.invalidateAllKeys();
-            }
+            latinKeyboard.setShifted(shifted);
         }
+        mKeyboardView.invalidateAllKeys();
     }
 
-    private void setShiftLocked(boolean shiftLocked) {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setShiftLocked(boolean shiftLocked) {
+        mInputMethodService.mHandler.cancelUpdateShiftState();
         LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
-            mKeyboardView.invalidateAllKeys();
+        if (latinKeyboard == null)
+            return;
+        latinKeyboard.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();
         }
     }
 
@@ -441,320 +352,93 @@
      * Toggle keyboard shift state triggered by user touch event.
      */
     public void toggleShift() {
-        mInputMethodService.mHandler.cancelUpdateShiftState();
-        if (DEBUG_STATE)
-            Log.d(TAG, "toggleShift:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        if (isAlphabetMode()) {
-            setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
-        } else {
-            toggleShiftInSymbol();
-        }
+        mState.onToggleShift();
     }
 
+    /**
+     * Toggle caps lock state triggered by user touch event.
+     */
     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);
-            }
-        }
+        mState.onToggleCapsLock();
     }
 
-    private void setAutomaticTemporaryUpperCase() {
-        if (mKeyboardView == null) return;
-        final Keyboard keyboard = mKeyboardView.getKeyboard();
-        if (keyboard == null) return;
-        keyboard.setAutomaticTemporaryUpperCase();
-        mKeyboardView.invalidateAllKeys();
+    /**
+     * Toggle between alphabet and symbols modes triggered by user touch event.
+     */
+    public void toggleAlphabetAndSymbols() {
+        mState.onToggleAlphabetAndSymbols();
     }
 
     /**
      * 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();
-        }
-    }
-
-    public void changeKeyboardMode() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "changeKeyboardMode:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState);
-        toggleKeyboardMode();
-        if (isShiftLocked() && isAlphabetMode())
-            setShiftLocked(true);
-        updateShiftState();
+        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
     }
 
     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;
-        }
+        mState.onPressShift(withSliding);
     }
 
     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();
+        mState.onReleaseShift(withSliding);
     }
 
     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;
+        mState.onPressSymbol();
     }
 
     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();
+        mState.onReleaseSymbol();
     }
 
     public void onOtherKeyPressed() {
-        if (DEBUG_STATE)
-            Log.d(TAG, "onOtherKeyPressed:"
-                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + mShiftKeyState
-                    + " symbolKeyState=" + mSymbolKeyState);
-        mShiftKeyState.onOtherKeyPressed();
-        mSymbolKeyState.onOtherKeyPressed();
+        mState.onOtherKeyPressed();
     }
 
     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(getKeyboard(mSymbolsKeyboardId));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetKeyboard() {
+        setKeyboard(getKeyboard(mMainKeyboardId));
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsShiftedKeyboard() {
+        setKeyboard(getKeyboard(mSymbolsShiftedKeyboardId));
     }
 
     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.
      */
-    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());
     }
 
     public LatinKeyboardView getKeyboardView() {
@@ -804,13 +488,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();
             }
         });
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 04e6725..d2741ed 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);
             }
         }
 
@@ -830,9 +829,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 +857,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 +866,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)
@@ -906,12 +905,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
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 7620396..a9fcd9a 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -176,7 +176,7 @@
 
     @Override
     public CharSequence adjustLabelCase(CharSequence label) {
-        if (isAlphaKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label)
+        if (mId.isAlphabetKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label)
                 && label.length() < 3 && Character.isLowerCase(label.charAt(0))) {
             return label.toString().toUpperCase(mId.mLocale);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 6ce3876..af5f808 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.os.Message;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -75,7 +74,7 @@
 
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
-    private int mOldKeyIndex;
+    private Key mOldKey;
 
     private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
     protected KeyDetector mKeyDetector;
@@ -90,6 +89,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 +103,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 +128,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 +139,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();
@@ -166,12 +177,13 @@
         public boolean onDoubleTap(MotionEvent firstDown) {
             final Keyboard keyboard = getKeyboard();
             if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard
-                    && ((LatinKeyboard) keyboard).isAlphaKeyboard()) {
+                    && ((LatinKeyboard) 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,8 +200,9 @@
                 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} .
@@ -264,20 +277,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.
@@ -329,7 +328,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,7 +337,6 @@
         // 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);
@@ -349,9 +347,11 @@
         // 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) {
+            mKeyboardActionListener.onCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
+        } else {
+            mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+        }
     }
 
     // This default implementation returns a more keys panel.
@@ -396,14 +396,14 @@
         final Keyboard keyboard = getKeyboard();
         if (keyboard instanceof LatinKeyboard) {
             final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
-            if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
+            if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.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_SHIFT && latinKeyboard.isAlphaKeyboard()) {
+            if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.mId.isAlphabetKeyboard()) {
                 tracker.onLongPressed();
                 invokeCodeInput(Keyboard.CODE_CAPSLOCK);
                 invokeReleaseKey(primaryCode);
@@ -463,8 +463,7 @@
                 this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener());
         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;
     }
@@ -548,8 +547,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 +558,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);
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
index f2c5b7b..8e99296 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
@@ -132,9 +132,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..9e0c5ce 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,7 +28,6 @@
 import com.android.inputmethod.latin.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 public class PointerTracker {
@@ -67,22 +67,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 +103,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;
 
@@ -119,9 +126,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 +174,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 +216,7 @@
 
     public static void dismissAllKeyPreviews() {
         for (final PointerTracker tracker : sTrackers) {
-            tracker.setReleasedKeyGraphics(tracker.mKeyIndex);
+            tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
         }
     }
 
@@ -228,12 +237,15 @@
 
     // 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)
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onPress    : " + KeyDetector.printableCode(key.mCode)
+                    + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
+                    + " enabled=" + key.isEnabled());
+        }
+        if (ignoreModifierKey) {
             return false;
+        }
         if (key.isEnabled()) {
             mListener.onPress(key.mCode, withSliding);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
@@ -246,35 +258,47 @@
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
-                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
-                    + " ignoreModifier=" + ignoreModifierKey);
-        if (ignoreModifierKey)
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+        final boolean alterCode = key.altCodeWhileTyping() && mTimerProxy.isTyping();
+        final int code = alterCode ? key.mAltCode : primaryCode;
+        // If code is CODE_DUMMY here, this key will be ignored or generate text.
+        final CharSequence text = (code != Keyboard.CODE_DUMMY) ? null : key.mOutputText;
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onCodeInput: " + KeyDetector.printableCode(code) + " text=" + text
+                    + " 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_DUMMY) {
+                mListener.onCodeInput(code, keyCodes, x, y);
+            } else if (text != null) {
+                mListener.onTextInput(text);
+            }
+            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  : " + KeyDetector.printableCode(primaryCode)
+                    + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
+                    + " enabled="+ key.isEnabled());
+        }
+        if (ignoreModifierKey) {
             return;
-        if (key.isEnabled())
+        }
+        if (key.isEnabled()) {
             mListener.onRelease(primaryCode, withSliding);
+        }
     }
 
     private void callListenerOnCancelInput() {
@@ -295,71 +319,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 +396,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 +469,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 +481,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, false)) {
+                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 +521,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, true)) {
+                    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, true)) {
+                        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 +570,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 +597,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 +632,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 +641,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 +675,7 @@
     private void onCancelEventInternal() {
         mTimerProxy.cancelKeyTimers();
         mDrawingProxy.cancelShowKeyPreview(this);
-        setReleasedKeyGraphics(mKeyIndex);
+        setReleasedKeyGraphics(mCurrentKey);
         mIsInSlidingKeyInput = false;
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
@@ -656,48 +683,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 +729,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..778aac3 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -65,26 +65,26 @@
         return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptyList(), 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,
@@ -157,7 +157,7 @@
         }
     }
 
-    public int getNativeProximityInfo() {
+    public long getNativeProximityInfo() {
         return mNativeProximityInfo;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index b385b7a..2187935 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -40,7 +40,6 @@
         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 {
@@ -68,11 +67,6 @@
             return a.getInt(index, defaultValue);
         }
 
-        @Override
-        public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
-            return a.getBoolean(index, defaultValue);
-        }
-
         protected static CharSequence[] parseTextArray(TypedArray a, int index) {
             if (!a.hasValue(index))
                 return null;
@@ -151,12 +145,6 @@
             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() {
             super();
         }
@@ -164,18 +152,18 @@
         private 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_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,11 +182,6 @@
                 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)
@@ -235,7 +218,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/KeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
index 28a53ce..11fb91a 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) {
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..292194b4
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -0,0 +1,515 @@
+/*
+ * 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;
+
+// TODO: Add unit tests
+/**
+ * Keyboard state machine.
+ *
+ * This class contains all keyboard state transition logic.
+ *
+ * The input events are {@link #onLoadKeyboard(String, boolean)}, {@link #onSaveKeyboardState()},
+ * {@link #onPressShift(boolean)}, {@link #onReleaseShift(boolean)}, {@link #onPressSymbol()},
+ * {@link #onReleaseSymbol()}, {@link #onOtherKeyPressed()}, {@link #onCodeInput(int, boolean)},
+ * {@link #onCancelInput(boolean)}, {@link #onUpdateShiftState(boolean)}, {@link #onToggleShift()},
+ * {@link #onToggleCapsLock()}, and {@link #onToggleAlphabetAndSymbols()}.
+ *
+ * The actions are {@link SwitchActions}'s methods.
+ */
+public class KeyboardState {
+    private static final String TAG = KeyboardState.class.getSimpleName();
+    private static final boolean DEBUG_STATE = 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();
+    }
+
+    private KeyboardShiftState mKeyboardShiftState = new KeyboardShiftState();
+
+    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 mHasDistinctMultitouch;
+
+    private final SwitchActions mSwitchActions;
+
+    private boolean mIsAlphabetMode;
+    private boolean mIsSymbolShifted;
+
+    private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
+    private boolean mPrevMainKeyboardWasShiftLocked;
+
+    static class SavedKeyboardState {
+        public boolean mIsValid;
+        public boolean mIsAlphabetMode;
+        public boolean mIsShiftLocked;
+        public boolean mIsShifted;
+    }
+
+    public KeyboardState(SwitchActions switchActions) {
+        mSwitchActions = switchActions;
+    }
+
+    public void onLoadKeyboard(String layoutSwitchBackSymbols, boolean hasDistinctMultitouch) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onLoadKeyboard");
+        }
+        mLayoutSwitchBackSymbols = layoutSwitchBackSymbols;
+        mHasDistinctMultitouch = hasDistinctMultitouch;
+        mKeyboardShiftState.setShifted(false);
+        mKeyboardShiftState.setShiftLocked(false);
+        mShiftKeyState.onRelease();
+        mSymbolKeyState.onRelease();
+        mPrevMainKeyboardWasShiftLocked = false;
+        onRestoreKeyboardState();
+    }
+
+    public void onSaveKeyboardState() {
+        final SavedKeyboardState state = mSavedKeyboardState;
+        state.mIsAlphabetMode = mIsAlphabetMode;
+        if (mIsAlphabetMode) {
+            state.mIsShiftLocked = mKeyboardShiftState.isShiftLocked();
+            state.mIsShifted = !state.mIsShiftLocked
+                    && mKeyboardShiftState.isShiftedOrShiftLocked();
+        } else {
+            state.mIsShiftLocked = false;
+            state.mIsShifted = mIsSymbolShifted;
+        }
+        state.mIsValid = true;
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onSaveKeyboardState: alphabet=" + state.mIsAlphabetMode
+                    + " shiftLocked=" + state.mIsShiftLocked + " shift=" + state.mIsShifted);
+        }
+    }
+
+    private void onRestoreKeyboardState() {
+        final SavedKeyboardState state = mSavedKeyboardState;
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onRestoreKeyboardState: valid=" + state.mIsValid
+                    + " alphabet=" + state.mIsAlphabetMode
+                    + " shiftLocked=" + state.mIsShiftLocked + " shift=" + state.mIsShifted);
+        }
+        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 mKeyboardShiftState.isShiftLocked();
+    }
+
+    private void setShifted(int shiftMode) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode));
+        }
+        if (shiftMode == SwitchActions.AUTOMATIC_SHIFT) {
+            mKeyboardShiftState.setAutomaticTemporaryUpperCase();
+        } else {
+            // TODO: Duplicated logic in KeyboardSwitcher#setShifted()
+            final boolean shifted = (shiftMode == SwitchActions.MANUAL_SHIFT);
+            // 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 (!mHasDistinctMultitouch && !shifted && mKeyboardShiftState.isShiftLocked()) {
+                mKeyboardShiftState.setShiftLocked(false);
+            }
+            mKeyboardShiftState.setShifted(shifted);
+        }
+        mSwitchActions.setShifted(shiftMode);
+    }
+
+    private void setShiftLocked(boolean shiftLocked) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked);
+        }
+        mKeyboardShiftState.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_STATE) {
+            Log.d(TAG, "setAlphabetKeyboard");
+        }
+        mSwitchActions.setAlphabetKeyboard();
+        mIsAlphabetMode = true;
+        mIsSymbolShifted = false;
+        mSwitchState = SWITCH_STATE_ALPHA;
+        setShiftLocked(mPrevMainKeyboardWasShiftLocked);
+        mPrevMainKeyboardWasShiftLocked = false;
+    }
+
+    private void setSymbolsKeyboard() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "setSymbolsKeyboard");
+        }
+        mPrevMainKeyboardWasShiftLocked = mKeyboardShiftState.isShiftLocked();
+        mSwitchActions.setSymbolsKeyboard();
+        mIsAlphabetMode = false;
+        mIsSymbolShifted = false;
+        mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+    }
+
+    private void setSymbolsShiftedKeyboard() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "setSymbolsShiftedKeyboard");
+        }
+        mSwitchActions.setSymbolsShiftedKeyboard();
+        mIsAlphabetMode = false;
+        mIsSymbolShifted = true;
+        mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+    }
+
+    public void onPressSymbol() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onPressSymbol: " + this);
+        }
+        toggleAlphabetAndSymbols();
+        mSymbolKeyState.onPress();
+        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
+    }
+
+    public void onReleaseSymbol() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onReleaseSymbol: " + this);
+        }
+        // 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) {
+            toggleAlphabetAndSymbols();
+        }
+        mSymbolKeyState.onRelease();
+    }
+
+    public void onOtherKeyPressed() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onOtherKeyPressed: " + this);
+        }
+        mShiftKeyState.onOtherKeyPressed();
+        mSymbolKeyState.onOtherKeyPressed();
+    }
+
+    public void onUpdateShiftState(boolean autoCaps) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onUpdateShiftState: " + this + " autoCaps=" + autoCaps);
+        }
+        if (mIsAlphabetMode) {
+            if (!mKeyboardShiftState.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();
+        }
+    }
+
+    public void onPressShift(boolean withSliding) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onPressShift: " + this + " sliding=" + withSliding);
+        }
+        if (mIsAlphabetMode) {
+            if (mKeyboardShiftState.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 (mKeyboardShiftState.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 (mKeyboardShiftState.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();
+        }
+    }
+
+    public void onReleaseShift(boolean withSliding) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onReleaseShift: " + this + " sliding=" + withSliding);
+        }
+        if (mIsAlphabetMode) {
+            final boolean isShiftLocked = mKeyboardShiftState.isShiftLocked();
+            if (mShiftKeyState.isMomentary()) {
+                // After chording input while normal state.
+                setShifted(SwitchActions.UNSHIFT);
+            } else if (isShiftLocked && !mKeyboardShiftState.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 (mKeyboardShiftState.isShiftedOrShiftLocked()
+                    && mShiftKeyState.isPressingOnShifted() && !withSliding) {
+                // Shift has been pressed without chording while shifted state.
+                setShifted(SwitchActions.UNSHIFT);
+            } else if (mKeyboardShiftState.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, 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) {
+                toggleShiftInSymbols();
+            }
+        }
+        mShiftKeyState.onRelease();
+    }
+
+    public void onCancelInput(boolean isSinglePointer) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onCancelInput: isSinglePointer=" + isSinglePointer + " " + this);
+        }
+        // Snap 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) {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onCodeInput: code=" + code + " isSinglePointer=" + isSinglePointer
+                    + " " + this);
+        }
+        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) {
+                // 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}.
+                toggleAlphabetAndSymbols();
+            } 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 (isSinglePointer) {
+                // 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.
+                toggleShiftInSymbols();
+                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)) {
+                setAlphabetKeyboard();
+            }
+            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)) {
+                setAlphabetKeyboard();
+            }
+            break;
+        }
+    }
+
+    public void onToggleShift() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onToggleShift: " + this);
+        }
+        if (mIsAlphabetMode) {
+            setShifted(mKeyboardShiftState.isShiftedOrShiftLocked()
+                    ? SwitchActions.UNSHIFT : SwitchActions.MANUAL_SHIFT);
+        } else {
+            toggleShiftInSymbols();
+        }
+    }
+
+    public void onToggleCapsLock() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onToggleCapsLock: " + this);
+        }
+        if (mIsAlphabetMode) {
+            if (mKeyboardShiftState.isShiftLocked()) {
+                setShiftLocked(false);
+                // Shift key is long pressed while caps lock state, we will toggle back to normal
+                // state. And mark as if shift key is released.
+                mShiftKeyState.onRelease();
+            } else {
+                setShiftLocked(true);
+            }
+        }
+    }
+
+    public void onToggleAlphabetAndSymbols() {
+        if (DEBUG_STATE) {
+            Log.d(TAG, "onToggleAlphabetAndSymbols: " + this);
+        }
+        toggleAlphabetAndSymbols();
+    }
+
+    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=" + mKeyboardShiftState
+                + " 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/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..cd066a3 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -98,7 +98,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;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b9fd574..f0e56d3 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,15 +107,15 @@
         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);
 
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/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7ba7f7d..5d075b1 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -62,6 +62,7 @@
 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;
@@ -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;
 
@@ -143,6 +143,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 +158,23 @@
         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 View mExtractArea;
     private View mKeyPreviewBackingView;
@@ -177,7 +194,7 @@
     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}.
@@ -190,12 +207,6 @@
     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;
@@ -210,11 +221,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 +250,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 +259,6 @@
         private int mDurationOfFadeoutLanguageOnSpacebar;
         private float mFinalFadeoutFactorOfLanguageOnSpacebar;
         private long mDoubleSpacesTurnIntoPeriodTimeout;
-        private long mIgnoreSpecialKeyTimeout;
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -271,8 +278,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
@@ -384,28 +389,22 @@
             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);
             resetPendingImsCallback();
             mIsOrientationChanging = true;
             final LatinIME latinIme = getOuterInstance();
-            latinIme.mKeyboardSwitcher.saveKeyboardState();
+            if (latinIme.isInputViewShown()) {
+                latinIme.mKeyboardSwitcher.saveKeyboardState();
+            }
         }
 
         private void resetPendingImsCallback() {
@@ -414,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;
@@ -436,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) {
@@ -466,6 +467,7 @@
             } else {
                 final LatinIME latinIme = getOuterInstance();
                 latinIme.onFinishInputViewInternal(finishingInput);
+                mAppliedEditorInfo = null;
             }
         }
 
@@ -544,10 +546,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() {
@@ -572,7 +572,7 @@
 
         mUserDictionary = new UserDictionary(this, localeStr);
         mSuggest.setUserDictionary(mUserDictionary);
-        mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
+        mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
 
         resetContactsDictionary(oldContactsDictionary);
 
@@ -693,13 +693,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
@@ -712,19 +712,19 @@
         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)));
         }
         // In landscape mode, this method gets called without the input view being created.
         if (inputView == null) {
@@ -734,7 +734,7 @@
         // 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();
@@ -745,22 +745,21 @@
         // 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);
+        initializeInputAttributes(editorInfo);
 
         inputView.closing();
         mEnteredText = null;
         mComposingStringBuilder.setLength(0);
         mHasUncommittedTypedChars = false;
         mDeleteCount = 0;
-        mJustAddedMagicSpace = false;
-        mJustReplacedDoubleSpace = false;
+        mSpaceState = SPACE_STATE_NONE;
 
         loadSettings();
         updateCorrectionMode();
@@ -769,12 +768,12 @@
         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)
@@ -783,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);
@@ -793,10 +793,10 @@
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
 
-    private void initializeInputAttributes(EditorInfo attribute) {
-        if (attribute == null)
+    private void initializeInputAttributes(EditorInfo editorInfo) {
+        if (editorInfo == null)
             return;
-        final int inputType = attribute.inputType;
+        final int inputType = editorInfo.inputType;
         if (inputType == InputType.TYPE_NULL) {
             // TODO: We should honor TYPE_NULL specification.
             Log.i(TAG, "InputType.TYPE_NULL is specified");
@@ -805,7 +805,7 @@
         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));
+                    inputType, editorInfo.imeOptions));
         }
 
         mInsertSpaceOnPickSuggestionManually = false;
@@ -921,6 +921,13 @@
                 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
         final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
         if (!mExpectingUpdateSelection) {
+            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 (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
                     || mVoiceProxy.isVoiceInputHighlighted())
                     && (selectionChanged || candidatesCleared)) {
@@ -938,22 +945,19 @@
                 TextEntryState.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
@@ -1164,25 +1168,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))
@@ -1190,22 +1191,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) {
@@ -1213,11 +1211,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);
@@ -1233,12 +1230,8 @@
         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() {
@@ -1254,6 +1247,7 @@
 
     // 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) {
@@ -1265,6 +1259,9 @@
                 return true;
             }
             return false;
+        case CODE_HAPTIC_AND_AUDIO_FEEDBACK:
+            hapticAndAudioFeedback(Keyboard.CODE_UNSPECIFIED);
+            return true;
         }
         return false;
     }
@@ -1273,6 +1270,28 @@
         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) {
+            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) {
@@ -1283,12 +1302,22 @@
         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();
@@ -1298,14 +1327,12 @@
             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();
+                switcher.toggleAlphabetAndSymbols();
             }
-            shouldStartKeyTypedTimer = false;
             break;
         case Keyboard.CODE_CANCEL:
             if (!isShowingOptionDialog()) {
@@ -1313,24 +1340,14 @@
             }
             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);
+            hapticAndAudioFeedback(primaryCode);
             break;
         case Keyboard.CODE_SHORTCUT:
-            if (!mHandler.isIgnoringSpecialKey()) {
-                mSubtypeSwitcher.switchToShortcutIME();
-            }
-            shouldStartKeyTypedTimer = false;
+            mSubtypeSwitcher.switchToShortcutIME();
             break;
         case Keyboard.CODE_TAB:
             handleTab();
@@ -1344,20 +1361,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
@@ -1371,10 +1386,9 @@
         ic.commitText(text, 1);
         ic.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
-        mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
-        mJustAddedMagicSpace = false;
+        mKeyboardSwitcher.onCodeInput(Keyboard.CODE_DUMMY);
+        mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
-        mHandler.startKeyTypedTimer();
     }
 
     @Override
@@ -1383,7 +1397,7 @@
         mKeyboardSwitcher.onCancelInput();
     }
 
-    private void handleBackspace(boolean justReplacedDoubleSpace) {
+    private void handleBackspace(final int spaceState) {
         if (mVoiceProxy.logAndRevertVoiceInput()) return;
 
         final InputConnection ic = getCurrentInputConnection();
@@ -1421,15 +1435,24 @@
         }
         mHandler.postUpdateShiftKeyState();
 
+        // TODO: Merge space state with TextEntryState
         TextEntryState.backspace();
         if (TextEntryState.isUndoCommit()) {
             revertLastWord(ic);
             ic.endBatchEdit();
             return;
         }
-        if (justReplacedDoubleSpace) {
+        if (SPACE_STATE_DOUBLE == spaceState) {
             if (revertDoubleSpace(ic)) {
                 ic.endBatchEdit();
+                // 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)) {
+                ic.endBatchEdit();
+                // Likewise
                 return;
             }
         }
@@ -1447,10 +1470,11 @@
                 // inconsistent with backspacing after selecting other suggestions.
                 revertLastWord(ic);
             } else {
-                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+                ic.deleteSurroundingText(1, 0);
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
-                    sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+                    ic.deleteSurroundingText(1, 0);
                 }
+                restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
             }
         }
         ic.endBatchEdit();
@@ -1479,18 +1503,24 @@
         }
     }
 
-    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();
 
-        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
-            removeTrailingSpace();
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic != null) ic.beginBatchEdit();
+        if (SPACE_STATE_MAGIC == spaceState
+                && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+            removeTrailingSpaceWhileInBatchEdit(ic);
         }
 
         int code = primaryCode;
         if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
                 && isSuggestionsRequested() && !isCursorTouchingWord()) {
             if (!mHasUncommittedTypedChars) {
-                mHasUncommittedTypedChars = true;
+                // Reset entirely the composing state anyway, then start composing a new word unless
+                // the character is a single quote.
+                mHasUncommittedTypedChars = (Keyboard.CODE_SINGLE_QUOTE != code);
                 mComposingStringBuilder.setLength(0);
                 mWordComposer.reset();
                 clearSuggestions();
@@ -1501,6 +1531,7 @@
         if (switcher.isShiftedOrShiftLocked()) {
             if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
                     || keyCodes[0] > Character.MAX_CODE_POINT) {
+                if (null != ic) ic.endBatchEdit();
                 return;
             }
             code = keyCodes[0];
@@ -1514,6 +1545,7 @@
                 } else {
                     // Some keys, such as [eszett], have upper case as multi-characters.
                     onTextInput(upperCaseString);
+                    if (null != ic) ic.endBatchEdit();
                     return;
                 }
             }
@@ -1521,7 +1553,6 @@
         if (mHasUncommittedTypedChars) {
             mComposingStringBuilder.append((char) code);
             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) {
@@ -1539,18 +1570,18 @@
         } 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 (null != ic) ic.endBatchEdit();
     }
 
-    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();
 
@@ -1580,21 +1611,49 @@
             }
         }
 
-        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);
+
+        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();
         }
 
         TextEntryState.typedCharacter((char) primaryCode, true, x, y);
@@ -1607,16 +1666,6 @@
                         ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
             }
         }
-        if (Keyboard.CODE_SPACE == primaryCode) {
-            if (!isCursorTouchingWord()) {
-                mHandler.cancelUpdateSuggestions();
-                mHandler.postUpdateBigramPredictions();
-            }
-        } else {
-            // Set punctuation right away. onUpdateSelection will fire but tests whether it is
-            // already displayed or not, so it's okay.
-            setPunctuationSuggestions();
-        }
         mKeyboardSwitcher.updateShiftState();
         if (ic != null) {
             ic.endBatchEdit();
@@ -1651,7 +1700,7 @@
     public boolean isSuggestionsStripVisible() {
         if (mSuggestionsView == null)
             return false;
-        if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
+        if (mSuggestionsView.isShowingAddToDictionaryHint())
             return true;
         if (!isShowingSuggestionsStrip())
             return false;
@@ -1737,8 +1786,8 @@
             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(wordComposer,
+                prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo(), mCorrectionMode);
 
         boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
         final CharSequence typedWord = wordComposer.getTypedWord();
@@ -1748,15 +1797,23 @@
         // 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 = wordComposer.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();
 
         // 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
@@ -1829,7 +1886,6 @@
         mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
                 mSettingsValues.mWordSeparators);
 
-        final boolean recorrecting = TextEntryState.isRecorrecting();
         final InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
@@ -1859,8 +1915,8 @@
             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
@@ -1868,15 +1924,8 @@
             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();
             }
@@ -1900,7 +1949,7 @@
                 suggestion.toString(), index, suggestions.mWords);
         TextEntryState.acceptedSuggestion(mComposingStringBuilder.toString(), suggestion);
         // Follow it with a space
-        if (mInsertSpaceOnPickSuggestionManually && !recorrecting) {
+        if (mInsertSpaceOnPickSuggestionManually) {
             sendMagicSpace();
         }
 
@@ -1920,13 +1969,11 @@
                         || !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);
-        }
+        // 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);
         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,8 +1983,9 @@
             // take a noticeable delay to update them which may feel uneasy.
         }
         if (showingAddToDictionaryHint) {
-            if (mIsUserDictionaryAvaliable) {
-                mSuggestionsView.showAddToDictionaryHint(suggestion);
+            if (mIsUserDictionaryAvailable) {
+                mSuggestionsView.showAddToDictionaryHint(
+                        suggestion, mSettingsValues.mHintToSaveText);
             } else {
                 mHandler.postUpdateSuggestions();
             }
@@ -1982,7 +2030,7 @@
         final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
                 mSettingsValues.mWordSeparators);
         SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(sEmptyWordComposer,
-                prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
+                prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo(), mCorrectionMode);
 
         if (builder.size() > 0) {
             // Explicitly supply an empty typed word (the no-second-arg version of
@@ -2068,13 +2116,60 @@
         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
+    // "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 "I'|" and "I|" are okay
+        final CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+        if (TextUtils.isEmpty(word)) return;
+        if (word.length() == 1 && !Character.isLetter(word.charAt(0))) return;
+
+        // Okay, we are at the end of a word. Restart suggestions.
+        restartSuggestionsOnWordBeforeCursor(ic, word);
+    }
+
+    // "ic" must not be null
+    private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
+            final CharSequence word) {
+        mWordComposer.setComposingWord(word, mKeyboardSwitcher.getLatinKeyboard());
+        mComposingStringBuilder.setLength(0);
+        mComposingStringBuilder.append(word);
+        // mBestWord will be set appropriately by updateSuggestions() called by the handler
+        mBestWord = null;
+        mHasUncommittedTypedChars = true;
+        mComposingStateManager.onStartComposingText();
+        TextEntryState.restartSuggestionsOnWordBeforeCursor();
+        ic.deleteSurroundingText(word.length(), 0);
+        ic.setComposingText(word, 1);
+        mHandler.postUpdateSuggestions();
+    }
+
+    // "ic" must not be null
     private void revertLastWord(final InputConnection ic) {
         if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -2100,6 +2195,10 @@
             // Clear composing text
             mComposingStringBuilder.setLength(0);
         } else {
+            // Note: this relies on the last word still being held in the WordComposer
+            // 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.
             mHasUncommittedTypedChars = true;
             ic.setComposingText(mComposingStringBuilder, 1);
             TextEntryState.backspace();
@@ -2108,7 +2207,7 @@
         mHandler.postUpdateSuggestions();
     }
 
-    // "ic" must not null
+    // "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
@@ -2123,13 +2222,28 @@
         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))
+            return false;
+        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();
     }
 
@@ -2155,12 +2269,16 @@
         loadSettings();
     }
 
+    private void hapticAndAudioFeedback(int primaryCode) {
+        vibrate();
+        playKeyClick(primaryCode);
+    }
+
     @Override
     public void onPress(int primaryCode, boolean withSliding) {
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         if (switcher.isVibrateAndSoundFeedbackRequired()) {
-            vibrate();
-            playKeyClick(primaryCode);
+            hapticAndAudioFeedback(primaryCode);
         }
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
         if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
@@ -2198,11 +2316,6 @@
         }
     };
 
-    // update keypress sound volume
-    private void updateSoundEffectVolume() {
-        mFxVolume = Utils.getCurrentKeypressSoundVolume(mPrefs, mResources);
-    }
-
     // update flags for silent mode
     private void updateRingerMode() {
         if (mAudioManager == null) {
@@ -2212,10 +2325,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
@@ -2240,7 +2349,7 @@
                 sound = AudioManager.FX_KEYPRESS_STANDARD;
                 break;
             }
-            mAudioManager.playSoundEffect(sound, mFxVolume);
+            mAudioManager.playSoundEffect(sound, mSettingsValues.mFxVolume);
         }
     }
 
@@ -2248,7 +2357,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) {
@@ -2257,7 +2366,7 @@
                         HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
             }
         } else if (mVibrator != null) {
-            mVibrator.vibrate(mKeypressVibrationDuration);
+            mVibrator.vibrate(mSettingsValues.mKeypressVibrationDuration);
         }
     }
 
@@ -2273,21 +2382,13 @@
         // 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)
+        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));
+        final String suggestionVisiblityStr = mSettingsValues.mShowSuggestionsSetting;
         for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
             if (suggestionVisiblityStr.equals(res.getString(visibility))) {
                 mSuggestionVisibility = visibility;
@@ -2375,7 +2476,9 @@
 
         final Printer p = new PrintWriterPrinter(fout);
         p.println("LatinIME state :");
-        p.println("  Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
+        final Keyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
+        final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
+        p.println("  Keyboard mode = " + keyboardMode);
         p.println("  mComposingStringBuilder=" + mComposingStringBuilder.toString());
         p.println("  mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
         p.println("  mCorrectionMode=" + mCorrectionMode);
@@ -2388,22 +2491,4 @@
         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));
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index ae8eb37..cbac4d3 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -33,7 +33,7 @@
     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() {
@@ -78,4 +78,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..a3fe15d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -0,0 +1,327 @@
+/*
+ * 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;
+    private final boolean mUsabilityStudyMode;
+    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;
+    private final int mVibrationDurationSettingsRawValue;
+    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);
+        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, res);
+        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(prefs, 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(prefs, 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 SharedPreferences sp,
+            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 SharedPreferences sp,
+            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;
+    }
+
+    // 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,
+            final Resources res) {
+        // 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/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index caa5aac..e9ca390 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);
@@ -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/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java
index c25ecb3..10f5ec9 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/SuggestionsView.java
@@ -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;
@@ -172,7 +171,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) {
@@ -228,7 +226,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) {
@@ -445,7 +442,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,8 +461,8 @@
             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(
@@ -647,9 +644,9 @@
                 && mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView;
     }
 
-    public void showAddToDictionaryHint(CharSequence word) {
+    public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
         clear();
-        mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth());
+        mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText);
     }
 
     public boolean dismissAddToDictionaryHint() {
@@ -675,34 +672,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 +803,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/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 79b3bde..a6041b3 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -30,13 +30,10 @@
     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 final int PUNCTUATION_AFTER_ACCEPTED = 5;
+    private static final int SPACE_AFTER_ACCEPTED = 6;
+    private static final int SPACE_AFTER_PICKED = 7;
+    private static final int UNDO_COMMIT = 8;
 
     private static int sState = UNKNOWN;
     private static int sPreviousState = UNKNOWN;
@@ -79,27 +76,11 @@
     }
 
     public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
-        if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
-            setState(PICKED_RECORRECTION);
-        } else {
-            setState(PICKED_SUGGESTION);
-        }
+        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) {
@@ -123,7 +104,6 @@
             }
             break;
         case PICKED_SUGGESTION:
-        case PICKED_RECORRECTION:
             if (isSpace) {
                 setState(SPACE_AFTER_PICKED);
             } else if (isSeparator) {
@@ -136,7 +116,6 @@
         case START:
         case UNKNOWN:
         case SPACE_AFTER_ACCEPTED:
-        case PUNCTUATION_AFTER_WORD:
             if (!isSpace && !isSeparator) {
                 setState(IN_WORD);
             } else {
@@ -150,9 +129,6 @@
                 setState(IN_WORD);
             }
             break;
-        case RECORRECTING:
-            setState(START);
-            break;
         }
         RingCharBuffer.getInstance().push(c, x, y);
         if (isSeparator) {
@@ -170,34 +146,33 @@
         } else if (sState == UNDO_COMMIT) {
             setState(IN_WORD);
         }
+        // TODO: tidy up this logic. At the moment, for example, writing a word goes to
+        // ACCEPTED_DEFAULT, backspace will go to UNDO_COMMIT, another backspace will go to IN_WORD,
+        // and subsequent backspaces will leave the status at IN_WORD, even if the user backspaces
+        // past the end of the word. We are not in a word any more but the state is still IN_WORD.
         if (DEBUG) displayState("backspace");
     }
 
+    public static void restartSuggestionsOnWordBeforeCursor() {
+        if (UNKNOWN == sState || ACCEPTED_DEFAULT == sState) {
+            // Here we can come from pretty much any state, except the ones that we can't
+            // come from after backspace, so supposedly anything except UNKNOWN and
+            // ACCEPTED_DEFAULT. Note : we could be in UNDO_COMMIT if
+            // LatinIME#revertLastWord() was calling LatinIME#restartSuggestions...()
+            Log.e(TAG, "Strange state change : coming from state " + sState);
+        }
+        setState(IN_WORD);
+    }
+
     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);
     }
@@ -208,13 +183,10 @@
         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";
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 9e65667..3a1af93 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -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..8c28324 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -20,6 +20,7 @@
 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;
@@ -38,11 +39,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 +133,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 +163,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 +210,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..de7cb57 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -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);
@@ -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..75bc10c 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,12 +16,21 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+
 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;
@@ -31,20 +40,15 @@
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-
 import 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;
@@ -242,7 +246,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;
         }
@@ -465,7 +469,7 @@
             }
         }
 
-        public void writeBackSpace() {
+        public static void writeBackSpace() {
             UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
         }
 
@@ -504,32 +508,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(TAG, "No internal log file found.");
+                        return;
+                    }
+                    if (mIms.checkCallingOrSelfPermission(
+                                android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                                        != PackageManager.PERMISSION_GRANTED) {
+                        Log.w(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(TAG, e1);
+                        return;
+                    } catch (IOException e2) {
+                        Log.w(TAG, e2);
+                        return;
+                    }
+                    if (destFile == null || !destFile.exists()) {
+                        Log.w(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(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);
                 }
             });
         }
@@ -778,38 +839,6 @@
         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();
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index adc5637..44c89f7 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,9 +16,13 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.LatinKeyboard;
 
 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
@@ -41,7 +45,9 @@
     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.
      */
@@ -53,6 +59,7 @@
         mTypedWord = new StringBuilder(N);
         mXCoordinates = new int[N];
         mYCoordinates = new int[N];
+        mTrailingSingleQuotesCount = 0;
     }
 
     public WordComposer(WordComposer source) {
@@ -62,11 +69,12 @@
     public void init(WordComposer source) {
         mCodes = new ArrayList<int[]>(source.mCodes);
         mTypedWord = new StringBuilder(source.mTypedWord);
-        mXCoordinates = source.mXCoordinates;
-        mYCoordinates = source.mYCoordinates;
+        mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
+        mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
         mCapsCount = source.mCapsCount;
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
+        mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
     }
 
     /**
@@ -77,6 +85,7 @@
         mTypedWord.setLength(0);
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
+        mTrailingSingleQuotesCount = 0;
     }
 
     /**
@@ -126,6 +135,55 @@
         mIsFirstCharCapitalized = isFirstCharCapitalized(
                 newIndex, primaryCode, mIsFirstCharCapitalized);
         if (Character.isUpperCase(primaryCode)) mCapsCount++;
+        if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+            ++mTrailingSingleQuotesCount;
+        } else {
+            mTrailingSingleQuotesCount = 0;
+        }
+    }
+
+    /**
+     * Internal method to retrieve reasonable proximity info for a character.
+     */
+    private void addKeyInfo(final int codePoint, final LatinKeyboard 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 LatinKeyboard 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);
+        }
+    }
+
+    /**
+     * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
+     */
+    public void setComposingWord(final CharSequence word, final LatinKeyboard 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 +193,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];
@@ -158,6 +216,14 @@
         if (size() == 0) {
             mIsFirstCharCapitalized = false;
         }
+        if (mTrailingSingleQuotesCount > 0) {
+            --mTrailingSingleQuotesCount;
+        } else {
+            for (int i = mTypedWord.length() - 1; i >= 0; --i) {
+                if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+                ++mTrailingSingleQuotesCount;
+            }
+        }
     }
 
     /**
@@ -179,6 +245,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
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 095c2c5..1ffee0d 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];
@@ -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);
+                    hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
                 }
             } else {
                 if (DBG) {
@@ -244,15 +339,15 @@
                 final CharSequence bestSuggestion = mSuggestions.get(0);
                 final double normalizedScore =
                         Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
-                hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+                hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
                 if (DBG) {
                     Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
                     Log.i(TAG, "Normalized score = " + normalizedScore
-                            + " (threshold " + mLikelyThreshold
-                            + ") => hasLikelySuggestions = " + hasLikelySuggestions);
+                            + " (threshold " + mRecommendedThreshold
+                            + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
                 }
             }
-            return new Result(gatheredSuggestions, hasLikelySuggestions);
+            return new Result(gatheredSuggestions, hasRecommendedSuggestions);
         }
     }
 
@@ -273,13 +368,15 @@
         for (Dictionary dict : oldWhitelistDictionaries.values()) {
             dict.close();
         }
-        if (null != mContactsDictionary) {
-            // The synchronously loaded contacts dictionary should have been in one
-            // or several pools, but it is shielded against multiple closing and it's
-            // safe to call it several times.
-            final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
-            mContactsDictionary = null;
-            dictToClose.close();
+        synchronized(mUseContactsLock) {
+            if (null != mContactsDictionary) {
+                // The synchronously loaded contacts dictionary should have been in one
+                // or several pools, but it is shielded against multiple closing and it's
+                // safe to call it several times.
+                final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
+                mContactsDictionary = null;
+                dictToClose.close();
+            }
         }
         return false;
     }
@@ -295,7 +392,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 +413,16 @@
             mWhitelistDictionaries.put(localeStr, whitelistDictionary);
         }
         dictionaryCollection.addDictionary(whitelistDictionary);
-        if (null == mContactsDictionary) {
-            mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+        synchronized(mUseContactsLock) {
+            if (mUseContactsDictionary) {
+                if (null == mContactsDictionary) {
+                    mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+                }
+            }
+            dictionaryCollection.addDictionary(mContactsDictionary);
+            mDictionaryCollectionsList.add(
+                    new WeakReference<DictionaryCollection>(dictionaryCollection));
         }
-        // TODO: add a setting to use or not contacts when checking spelling
-        dictionaryCollection.addDictionary(mContactsDictionary);
         return new DictAndProximity(dictionaryCollection, proximityInfo);
     }
 
@@ -346,6 +450,8 @@
         private DictionaryPool mDictionaryPool;
         // Likewise
         private Locale mLocale;
+        // Cache this for performance
+        private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
 
         private final AndroidSpellCheckerService mService;
 
@@ -358,17 +464,51 @@
             final String localeString = getLocale();
             mDictionaryPool = mService.getDictionaryPool(localeString);
             mLocale = LocaleUtils.constructLocaleFromString(localeString);
+            mScript = getScriptFromLocale(mLocale);
+        }
+
+        /*
+         * Returns whether the code point is a letter that makes sense for the specified
+         * locale for this spell checker.
+         * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
+         * and is limited to EFIGS languages and Russian.
+         * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
+         * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
+         */
+        private static boolean isLetterCheckableByLanguage(final int codePoint,
+                final int script) {
+            switch (script) {
+            case SCRIPT_LATIN:
+                // Our supported latin script dictionaries (EFIGS) at the moment only include
+                // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
+                // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
+                // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
+                // excluded from isLetter anyway.
+                return codePoint <= 0x2AF && Character.isLetter(codePoint);
+            case SCRIPT_CYRILLIC:
+                // All Cyrillic characters are in the 400~52F block. There are some in the upper
+                // Unicode range, but they are archaic characters that are not used in modern
+                // russian and are not used by our dictionary.
+                return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+            default:
+                // Should never come here
+                throw new RuntimeException("Impossible value of script: " + script);
+            }
         }
 
         /**
          * Finds out whether a particular string should be filtered out of spell checking.
          *
-         * This will loosely match URLs, numbers, symbols.
+         * This will loosely match URLs, numbers, symbols. To avoid always underlining words that
+         * we know we will never recognize, this accepts a script identifier that should be one
+         * of the SCRIPT_* constants defined above, to rule out quickly characters from very
+         * different languages.
          *
          * @param text the string to evaluate.
+         * @param script the identifier for the script this spell checker recognizes
          * @return true if we should filter this text out, false otherwise
          */
-        private boolean shouldFilterOut(final String text) {
+        private static boolean shouldFilterOut(final String text, final int script) {
             if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
 
             // TODO: check if an equivalent processing can't be done more quickly with a
@@ -376,7 +516,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 +529,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 +548,7 @@
             try {
                 final String text = textInfo.getText();
 
-                if (shouldFilterOut(text)) {
+                if (shouldFilterOut(text, mScript)) {
                     DictAndProximity dictInfo = null;
                     try {
                         dictInfo = mDictionaryPool.takeOrGetNull();
@@ -426,17 +566,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 +621,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 +629,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..9a2bebf 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;
     }
+
+    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 private 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);
+        }
+        private static int getIndexOf(int characterCode) {
+            return computeIndex(characterCode, INDICES);
+        }
+    }
+
+    static class Cyrillic {
+        final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+        final private 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);
+        }
+        private 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/native/Android.mk b/native/Android.mk
index f07be6a..d2537f0 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -12,6 +12,7 @@
     jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \
     jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
     jni/jni_common.cpp \
+    src/basechars.cpp \
     src/bigram_dictionary.cpp \
     src/char_utils.cpp \
     src/correction.cpp \
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..42d0e32 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -33,6 +33,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,19 +42,19 @@
 
 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) {
+    const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
+    if (sourceDirChars == 0) {
         LOGE("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 */
@@ -66,7 +67,7 @@
     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);
         return 0;
@@ -74,9 +75,9 @@
     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) {
+    if (file == 0) {
         LOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
         return 0;
     }
@@ -107,7 +108,7 @@
         LOGE("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");
 #ifdef USE_MMAP_FOR_DICTIONARY
@@ -121,23 +122,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 +152,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 +176,19 @@
     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 void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
     Dictionary *dictionary = (Dictionary*)dict;
     if (!dictionary) return;
     void *dictBuf = dictionary->getDict();
@@ -217,11 +218,11 @@
 }
 
 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}
 };
 
 int register_BinaryDictionary(JNIEnv *env) {
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 8643f72..958abfd 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -32,14 +32,14 @@
  * 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");
         goto bail;
     }
-    assert(env != NULL);
+    assert(env != 0);
 
     if (!register_BinaryDictionary(env)) {
         LOGE("ERROR: BinaryDictionary native registration failed");
@@ -63,7 +63,7 @@
 int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* methods,
         int numMethods) {
     jclass clazz = env->FindClass(className);
-    if (clazz == NULL) {
+    if (clazz == 0) {
         LOGE("Native registration unable to find class '%s'", className);
         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/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..22ee75a 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -21,6 +21,7 @@
 
 #define LOG_TAG "LatinIME: correction.cpp"
 
+#include "char_utils.h"
 #include "correction.h"
 #include "dictionary.h"
 #include "proximity_info.h"
@@ -48,13 +49,13 @@
 
     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 uint32_t ci = toBaseLowerCase(input[i]);
+            const uint32_t co = 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])) {
+            if (i > 0 && j > 0 && ci == toBaseLowerCase(output[j - 1])
+                    && co == toBaseLowerCase(input[i - 1])) {
                 dp[(i + 1) * lo + (j + 1)] = min(
                         dp[(i + 1) * lo + (j + 1)], dp[(i - 1) * lo + (j - 1)] + cost);
             }
@@ -87,17 +88,15 @@
     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);
         }
     }
@@ -607,13 +606,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));
 }
 
 //////////////////////
diff --git a/native/src/dictionary.h b/native/src/dictionary.h
index d5de008..f891e74 100644
--- a/native/src/dictionary.h
+++ b/native/src/dictionary.h
@@ -17,7 +17,6 @@
 #ifndef LATINIME_DICTIONARY_H
 #define LATINIME_DICTIONARY_H
 
-#include "basechars.h"
 #include "bigram_dictionary.h"
 #include "char_utils.h"
 #include "defines.h"
@@ -63,7 +62,6 @@
     static int setDictionaryValues(const unsigned char *dict, const bool isLatestDictVersion,
             const int pos, unsigned short *c, int *childrenPosition,
             bool *terminal, int *freq);
-    static inline unsigned short toBaseLowerCase(unsigned short c);
 
 private:
     bool hasBigram();
@@ -156,19 +154,6 @@
     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..6857caf 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -47,7 +47,7 @@
           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];
@@ -167,7 +167,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 +232,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 +245,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..832db10 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -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];
     }
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 8eb5a97..647bfde 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -253,12 +253,6 @@
     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) {
@@ -470,8 +464,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 +477,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;
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index ef9709a..4f4fef2 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -23,10 +23,6 @@
 #include "defines.h"
 #include "proximity_info.h"
 
-#ifndef NULL
-#define NULL 0
-#endif
-
 namespace latinime {
 
 class UnigramDictionary {
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 464930f..06b1924 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -19,7 +19,6 @@
 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;
@@ -29,6 +28,7 @@
 
 public class SuggestHelper {
     protected final Suggest mSuggest;
+    protected int mCorrectionMode;
     protected final LatinKeyboard mKeyboard;
     private final KeyDetector mKeyDetector;
 
@@ -51,40 +51,23 @@
     }
 
     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/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
index 023e20a..863c2b2 100644
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
@@ -38,7 +38,7 @@
                 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);