Merge "Fix caps lock release in non-distinct multi touch device"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 7de41f0..3f99cda 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -123,6 +123,8 @@
         <!-- The hint icon to display on the key when keyboard is in manual temporary upper case
              mode. -->
         <attr name="manualTemporaryUpperCaseHintIcon" format="reference" />
+        <!-- The key style to specify a set of key attributes defined by <key_style/> -->
+        <attr name="keyStyle" format="string" />
     </declare-styleable>
 
     <declare-styleable name="BaseKeyboard_Row">
@@ -153,4 +155,9 @@
         <attr name="voiceKeyEnabled" format="string" />
         <attr name="hasVoiceKey" format="string" />
     </declare-styleable>
+
+    <declare-styleable name="BaseKeyboard_KeyStyle">
+        <attr name="styleName" format="string" />
+        <attr name="parentStyle" format="string" />
+    </declare-styleable>
 </resources>
diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index cbfe896..54fa610 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -23,7 +23,7 @@
     <integer name="key_return">10</integer>
     <integer name="key_space">32</integer>
     <integer name="key_shift">-1</integer>
-    <integer name="key_symbol">-2</integer>
+    <integer name="key_switch_alpha_symbol">-2</integer>
     <integer name="key_delete">-5</integer>
     <!-- Keycode for F1 (function) key. This one switches between language switch & comma/.com -->
     <integer name="key_settings">-100</integer>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 018a1f9..09449dc 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -219,11 +219,11 @@
     <!-- Label for soft enter key when it performs SEND action.  Must be short to fit on key! -->
     <string name="label_send_key">Send</string>
     <!-- Label for "switch to symbols" key.  Must be short to fit on key! -->
-    <string name="label_symbol_key">\?123</string>
+    <string name="label_to_symbol_key">\?123</string>
     <!-- Label for "switch to numeric" key.  Must be short to fit on key! -->
     <string name="label_phone_key">123</string>
     <!-- Label for "switch to alphabetic" key.  Must be short to fit on key! -->
-    <string name="label_alpha_key">ABC</string>
+    <string name="label_to_alpha_key">ABC</string>
     <!-- Label for ALT modifier key.  Must be short to fit on key! -->
     <string name="label_alt_key">ALT</string>
     <!-- Label for Backspace modifier key.  Must be short to fit on key! -->
diff --git a/java/res/xml-xlarge/kbd_phone.xml b/java/res/xml-xlarge/kbd_phone.xml
index f304996..4bbd69d 100644
--- a/java/res/xml-xlarge/kbd_phone.xml
+++ b/java/res/xml-xlarge/kbd_phone.xml
@@ -69,7 +69,7 @@
     </Row>
     <Row>
         <Key
-            latin:codes="@integer/key_symbol"
+            latin:codes="@integer/key_switch_alpha_symbol"
             latin:keyLabel="@string/label_more_key"
             latin:keyLabelOption="fontItalic|alignLeft|alignBottom"
             latin:isModifier="true"
diff --git a/java/res/xml-xlarge/kbd_phone_symbols.xml b/java/res/xml-xlarge/kbd_phone_symbols.xml
index 42a6c61..8f496c0 100644
--- a/java/res/xml-xlarge/kbd_phone_symbols.xml
+++ b/java/res/xml-xlarge/kbd_phone_symbols.xml
@@ -73,7 +73,7 @@
     </Row>
     <Row>
         <Key
-            latin:codes="@integer/key_symbol"
+            latin:codes="@integer/key_switch_alpha_symbol"
             latin:keyLabel="@string/label_more_key"
             latin:keyLabelOption="fontItalic|alignLeft|alignBottom"
             latin:isModifier="true"
diff --git a/java/res/xml-xlarge/kbd_qwerty_row2.xml b/java/res/xml-xlarge/kbd_qwerty_row2.xml
index 234aa0c..f1df3f2 100644
--- a/java/res/xml-xlarge/kbd_qwerty_row2.xml
+++ b/java/res/xml-xlarge/kbd_qwerty_row2.xml
@@ -25,8 +25,8 @@
         latin:keyWidth="8.157%p"
     >
         <Key
-            latin:codes="@integer/key_symbol"
-            latin:keyLabel="@string/label_symbol_key"
+            latin:codes="@integer/key_switch_alpha_symbol"
+            latin:keyLabel="@string/label_to_symbol_key"
             latin:keyLabelOption="fontItalic|alignLeft|alignBottom"
             latin:keyWidth="11.167%p"
             latin:isModifier="true"
diff --git a/java/res/xml-xlarge/kbd_symbols.xml b/java/res/xml-xlarge/kbd_symbols.xml
index 6ce8aad..99f4d81 100644
--- a/java/res/xml-xlarge/kbd_symbols.xml
+++ b/java/res/xml-xlarge/kbd_symbols.xml
@@ -82,8 +82,8 @@
         latin:keyWidth="8.157%p"
     >
         <Key
-            latin:codes="@integer/key_symbol"
-            latin:keyLabel="@string/label_alpha_key"
+            latin:codes="@integer/key_switch_alpha_symbol"
+            latin:keyLabel="@string/label_to_alpha_key"
             latin:keyLabelOption="fontItalic|alignLeft|alignBottom"
             latin:keyWidth="11.167%p"
             latin:isModifier="true"
diff --git a/java/res/xml-xlarge/kbd_symbols_shift.xml b/java/res/xml-xlarge/kbd_symbols_shift.xml
index 3f4cc26..016b001 100644
--- a/java/res/xml-xlarge/kbd_symbols_shift.xml
+++ b/java/res/xml-xlarge/kbd_symbols_shift.xml
@@ -72,8 +72,8 @@
         latin:keyWidth="8.157%p"
     >
         <Key
-            latin:codes="@integer/key_symbol"
-            latin:keyLabel="@string/label_alpha_key"
+            latin:codes="@integer/key_switch_alpha_symbol"
+            latin:keyLabel="@string/label_to_alpha_key"
             latin:keyLabelOption="fontItalic|alignLeft|alignBottom"
             latin:keyWidth="11.167%p"
             latin:isModifier="true"
diff --git a/java/res/xml/kbd_functional_key_style.xml b/java/res/xml/kbd_functional_key_style.xml
new file mode 100644
index 0000000..eabaa4a
--- /dev/null
+++ b/java/res/xml/kbd_functional_key_style.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <key-style
+        latin:styleName="functionalKeyStyle"
+        latin:isModifier="true" />
+    <key-style
+        latin:styleName="shiftKeyStyle"
+        latin:codes="@integer/key_shift"
+        latin:keyIcon="@drawable/sym_keyboard_shift"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_shift"
+        latin:parentStyle="functionalKeyStyle"
+        latin:isSticky="true" />
+    <key-style
+        latin:styleName="deleteKeyStyle"
+        latin:codes="@integer/key_delete"
+        latin:keyIcon="@drawable/sym_keyboard_delete"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_delete"
+        latin:parentStyle="functionalKeyStyle"
+        latin:isRepeatable="true" />
+    <switch>
+        <!-- When this qwerty keyboard has no voice key but voice key is enabled, then symbol
+             keyboard will have mic key. That means we should use "?123mic" key here. -->
+        <case
+            latin:voiceKeyEnabled="true"
+            latin:hasVoiceKey="false"
+        >
+            <key-style
+                latin:styleName="toSymbolKeyStyle"
+                latin:codes="@integer/key_switch_alpha_symbol"
+                latin:keyIcon="@drawable/sym_keyboard_123_mic"
+                latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
+                latin:parentStyle="functionalKeyStyle" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="toSymbolKeyStyle"
+                latin:codes="@integer/key_switch_alpha_symbol"
+                latin:keyLabel="@string/label_to_symbol_key"
+                latin:parentStyle="functionalKeyStyle" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="toAlphaKeyStyle"
+        latin:codes="@integer/key_switch_alpha_symbol"
+        latin:keyLabel="@string/label_to_alpha_key"
+        latin:parentStyle="functionalKeyStyle" />
+    <key-style
+        latin:styleName="settingsKeyStyle"
+        latin:codes="@integer/key_settings"
+        latin:keyIcon="@drawable/sym_keyboard_settings"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_settings"
+        latin:parentStyle="functionalKeyStyle" />
+    <key-style
+        latin:styleName="spaceKeyStyle"
+        latin:codes="@integer/key_space"
+        latin:keyIcon="@drawable/sym_keyboard_space"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_space"
+        latin:parentStyle="functionalKeyStyle" />
+    <key-style
+        latin:styleName="tabKeyStyle"
+        latin:codes="@integer/key_tab"
+        latin:keyIcon="@drawable/sym_keyboard_tab"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_tab"
+        latin:parentStyle="functionalKeyStyle" />
+    <key-style
+        latin:styleName="returnKeyStyle"
+        latin:codes="@integer/key_return"
+        latin:keyIcon="@drawable/sym_keyboard_return"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+        latin:parentStyle="functionalKeyStyle" />
+    <key-style
+        latin:styleName="altKeyStyle"
+        latin:codes="@integer/key_shift"
+        latin:keyLabel="@string/label_alt_key"
+        latin:parentStyle="functionalKeyStyle"
+        latin:isSticky="true" />
+    <key-style
+        latin:styleName="smileyKeyStyle"
+        latin:keyLabel=":-)"
+        latin:keyOutputText=":-) "
+        latin:keyHintIcon="@drawable/hint_popup"
+        latin:popupKeyboard="@xml/popup_smileys"
+        latin:parentStyle="functionalKeyStyle" />
+    <key-style
+        latin:styleName="micKeyStyle"
+        latin:codes="@integer/key_voice"
+        latin:popupKeyboard="@xml/popup_mic"
+        latin:keyIcon="@drawable/sym_keyboard_mic"
+        latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
+        latin:keyHintIcon="@drawable/hint_popup"
+        latin:parentStyle="functionalKeyStyle" />
+</merge>
diff --git a/java/res/xml/kbd_phone.xml b/java/res/xml/kbd_phone.xml
index e6f6a09..a01d697 100644
--- a/java/res/xml/kbd_phone.xml
+++ b/java/res/xml/kbd_phone.xml
@@ -25,6 +25,8 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:keyHeight="@dimen/key_height"
 >
+    <include
+        latin:keyboardLayout="@xml/kbd_functional_key_style" />
     <Row
         latin:rowEdgeFlags="top"
     >
@@ -40,8 +42,8 @@
             latin:keyIcon="@drawable/sym_keyboard_num3" />
         <Key
             latin:keyLabel="-"
+            latin:keyStyle="functionalKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row>
@@ -57,8 +59,8 @@
             latin:keyIcon="@drawable/sym_keyboard_num6" />
         <Key
             latin:keyLabel="."
+            latin:keyStyle="functionalKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row>
@@ -73,19 +75,15 @@
             latin:codes="57"
             latin:keyIcon="@drawable/sym_keyboard_num9" />
         <Key
-            latin:codes="@integer/key_delete"
-            latin:keyIcon="@drawable/sym_keyboard_delete"
-            latin:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
-            latin:isRepeatable="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
         latin:rowEdgeFlags="bottom"
     >
         <Key
-            latin:codes="@integer/key_symbol"
+            latin:codes="@integer/key_switch_alpha_symbol"
             latin:keyIcon="@drawable/sym_keyboard_numalt"
             latin:iconPreview="@drawable/sym_keyboard_feedback_numalt"
             latin:keyEdgeFlags="left" />
@@ -97,10 +95,8 @@
             latin:keyIcon="@drawable/sym_keyboard_space"
             latin:iconPreview="@drawable/sym_keyboard_feedback_space" />
         <Key
-            latin:codes="@integer/key_return"
-            latin:keyIcon="@drawable/sym_keyboard_return"
+            latin:keyStyle="returnKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
             latin:keyEdgeFlags="right" />
     </Row>
 </Keyboard>
diff --git a/java/res/xml/kbd_phone_black.xml b/java/res/xml/kbd_phone_black.xml
index 86759d6..75ce34a 100644
--- a/java/res/xml/kbd_phone_black.xml
+++ b/java/res/xml/kbd_phone_black.xml
@@ -82,7 +82,7 @@
         latin:rowEdgeFlags="bottom"
     >
         <Key
-            latin:codes="@integer/key_symbol"
+            latin:codes="@integer/key_switch_alpha_symbol"
             latin:keyIcon="@drawable/sym_bkeyboard_numalt"
             latin:iconPreview="@drawable/sym_keyboard_feedback_numalt" />
         <Key
diff --git a/java/res/xml/kbd_phone_symbols.xml b/java/res/xml/kbd_phone_symbols.xml
index 1c691d2..d53107a 100644
--- a/java/res/xml/kbd_phone_symbols.xml
+++ b/java/res/xml/kbd_phone_symbols.xml
@@ -25,6 +25,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:keyHeight="@dimen/key_height"
 >
+    <include latin:keyboardLayout="@xml/kbd_functional_key_style" />
     <Row
         latin:rowEdgeFlags="top"
     >
@@ -37,8 +38,8 @@
             latin:keyLabel=")" />
         <Key
             latin:keyLabel="-"
+            latin:keyStyle="functionalKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row>
@@ -54,8 +55,8 @@
             latin:keyLabel="," />
         <Key
             latin:keyLabel="."
+            latin:keyStyle="functionalKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row>
@@ -71,19 +72,15 @@
             latin:codes="35"
             latin:keyIcon="@drawable/sym_keyboard_numpound" />
         <Key
-            latin:codes="@integer/key_delete"
-            latin:keyIcon="@drawable/sym_keyboard_delete"
-            latin:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
-            latin:isRepeatable="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
         latin:rowEdgeFlags="bottom"
     >
         <Key
-            latin:codes="@integer/key_symbol"
+            latin:codes="@integer/key_switch_alpha_symbol"
             latin:keyLabel="@string/label_phone_key"
             latin:keyEdgeFlags="left" />
         <Key
@@ -93,10 +90,8 @@
             latin:keyIcon="@drawable/sym_keyboard_space"
             latin:iconPreview="@drawable/sym_keyboard_feedback_space" />
         <Key
-            latin:codes="@integer/key_return"
-            latin:keyIcon="@drawable/sym_keyboard_return"
+            latin:keyStyle="returnKeyStyle"
             latin:keyWidth="20%p"
-            latin:isModifier="true"
             latin:keyEdgeFlags="right" />
     </Row>
 </Keyboard>
diff --git a/java/res/xml/kbd_phone_symbols_black.xml b/java/res/xml/kbd_phone_symbols_black.xml
index 3e46c53..06db969 100644
--- a/java/res/xml/kbd_phone_symbols_black.xml
+++ b/java/res/xml/kbd_phone_symbols_black.xml
@@ -80,7 +80,7 @@
         latin:rowEdgeFlags="bottom"
     >
         <Key
-            latin:codes="@integer/key_symbol"
+            latin:codes="@integer/key_switch_alpha_symbol"
             latin:keyLabel="@string/label_phone_key"
             latin:keyEdgeFlags="left" />
         <Key
diff --git a/java/res/xml/kbd_qwerty.xml b/java/res/xml/kbd_qwerty.xml
index 517b4fe..aac4dfa 100644
--- a/java/res/xml/kbd_qwerty.xml
+++ b/java/res/xml/kbd_qwerty.xml
@@ -25,6 +25,8 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:keyHeight="@dimen/key_height"
 >
+    <include
+        latin:keyboardLayout="@xml/kbd_functional_key_style" />
     <Row
         latin:rowEdgeFlags="top"
     >
@@ -117,12 +119,8 @@
     </Row>
     <Row>
         <Key
-            latin:codes="@integer/key_shift"
-            latin:keyIcon="@drawable/sym_keyboard_shift"
-            latin:iconPreview="@drawable/sym_keyboard_feedback_shift"
+            latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
-            latin:isModifier="true"
-            latin:isSticky="true"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="z"
@@ -147,13 +145,10 @@
         <Key
             latin:keyLabel="m" />
         <Key
-            latin:codes="@integer/key_delete"
-            latin:keyIcon="@drawable/sym_keyboard_delete"
-            latin:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
-            latin:isModifier="true"
-            latin:isRepeatable="true"
             latin:keyEdgeFlags="right" />
     </Row>
-    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+    <include
+        latin:keyboardLayout="@xml/kbd_qwerty_row4" />
 </Keyboard>
diff --git a/java/res/xml/kbd_qwerty_black_symbol.xml b/java/res/xml/kbd_qwerty_black_symbol.xml
index c1ef170..840db9c 100644
--- a/java/res/xml/kbd_qwerty_black_symbol.xml
+++ b/java/res/xml/kbd_qwerty_black_symbol.xml
@@ -34,7 +34,7 @@
                     latin:hasVoiceKey="false"
                 >
                     <Key
-                        latin:codes="@integer/key_symbol"
+                        latin:codes="@integer/key_switch_alpha_symbol"
                         latin:keyIcon="@drawable/sym_bkeyboard_123_mic"
                         latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
                         latin:keyWidth="20%p"
@@ -43,7 +43,7 @@
                 </case>
                 <default>
                     <Key
-                        latin:codes="@integer/key_symbol"
+                        latin:codes="@integer/key_switch_alpha_symbol"
                         latin:keyLabel="@string/label_symbol_key"
                         latin:keyWidth="20%p"
                         latin:isModifier="true"
@@ -63,7 +63,7 @@
                     latin:hasVoiceKey="false"
                 >
                     <Key
-                        latin:codes="@integer/key_symbol"
+                        latin:codes="@integer/key_switch_alpha_symbol"
                         latin:keyIcon="@drawable/sym_bkeyboard_123_mic"
                         latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
                         latin:keyWidth="15%p"
@@ -72,7 +72,7 @@
                 </case>
                 <default>
                     <Key
-                        latin:codes="@integer/key_symbol"
+                        latin:codes="@integer/key_switch_alpha_symbol"
                         latin:keyLabel="@string/label_symbol_key"
                         latin:keyWidth="15%p"
                         latin:isModifier="true"
diff --git a/java/res/xml/kbd_qwerty_f1.xml b/java/res/xml/kbd_qwerty_f1.xml
index 23e494c..1f0ccfb 100644
--- a/java/res/xml/kbd_qwerty_f1.xml
+++ b/java/res/xml/kbd_qwerty_f1.xml
@@ -40,23 +40,24 @@
                 latin:keyHintIcon="@drawable/hint_popup"
                 latin:isModifier="true" />
         </case>
-        <case
-            latin:hasVoiceKey="true"
-        >
-            <Key
-                latin:codes="@integer/key_voice"
-                latin:popupKeyboard="@xml/popup_mic"
-                latin:keyIcon="@drawable/sym_keyboard_mic"
-                latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:isModifier="true" />
-        </case>
         <default>
-            <Key
-                latin:keyLabel=","
-                latin:popupKeyboard="@xml/popup_comma"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:isModifier="true" />
+            <switch>
+                <case
+                    latin:hasVoiceKey="true"
+                >
+                    <Key
+                        latin:keyStyle="micKeyStyle" />
+                </case>
+                <case
+                    latin:hasVoiceKey="false"
+                >
+                    <Key
+                        latin:keyLabel=","
+                        latin:popupKeyboard="@xml/popup_comma"
+                        latin:keyHintIcon="@drawable/hint_popup"
+                        latin:isModifier="true" />
+                </case>
+            </switch>
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/kbd_qwerty_row4.xml b/java/res/xml/kbd_qwerty_row4.xml
index a5113ea..cd03c51 100644
--- a/java/res/xml/kbd_qwerty_row4.xml
+++ b/java/res/xml/kbd_qwerty_row4.xml
@@ -29,8 +29,10 @@
             <case
                 latin:hasSettingsKey="false"
             >
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_symbol" />
+                <Key
+                    latin:keyStyle="toSymbolKeyStyle"
+                    latin:keyWidth="20%p"
+                    latin:keyEdgeFlags="left" />
                 <include
                     latin:keyboardLayout="@xml/kbd_qwerty_f1" />
                 <switch>
@@ -38,52 +40,36 @@
                         latin:mode="web"
                     >
                         <Key
-                            latin:codes="@integer/key_space"
-                            latin:keyIcon="@drawable/sym_keyboard_space"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                            latin:keyWidth="20%p"
-                            latin:isModifier="true" />
+                            latin:keyStyle="spaceKeyStyle"
+                            latin:keyWidth="20%p" />
                         <Key
-                            latin:codes="@integer/key_tab"
-                            latin:keyIcon="@drawable/sym_keyboard_tab"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_tab"
-                            latin:keyWidth="20%p"
-                            latin:isModifier="true" />
+                            latin:keyStyle="tabKeyStyle"
+                            latin:keyWidth="20%p" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_space"
-                            latin:keyIcon="@drawable/sym_keyboard_space"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                            latin:keyWidth="40%p"
-                            latin:isModifier="true" />
+                            latin:keyStyle="spaceKeyStyle"
+                            latin:keyWidth="40%p" />
                     </default>
                 </switch>
                 <Key
                     latin:keyLabel="."
                     latin:keyHintIcon="@drawable/hint_popup"
                     latin:popupKeyboard="@xml/popup_punctuation"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
                     >
                         <Key
-                            latin:keyLabel=":-)"
-                            latin:keyOutputText=":-) "
-                            latin:keyHintIcon="@drawable/hint_popup"
-                            latin:popupKeyboard="@xml/popup_smileys"
+                            latin:keyStyle="smileyKeyStyle"
                             latin:keyWidth="20%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="20%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </default>
                 </switch>
@@ -91,13 +77,12 @@
             <case
                 latin:hasSettingsKey="true"
             >
-                <include
-                    latin:keyboardLayout="@xml/kbd_qwerty_symbol" />
                 <Key
-                    latin:codes="@integer/key_settings"
-                    latin:keyIcon="@drawable/sym_keyboard_settings"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_settings"
-                    latin:isModifier="true" />
+                    latin:keyStyle="toSymbolKeyStyle"
+                    latin:keyWidth="15%p"
+                    latin:keyEdgeFlags="left" />
+                <Key
+                    latin:keyStyle="settingsKeyStyle" />
                 <include
                     latin:keyboardLayout="@xml/kbd_qwerty_f1" />
                 <switch>
@@ -105,62 +90,43 @@
                         latin:mode="web"
                     >
                         <Key
-                            latin:codes="@integer/key_space"
-                            latin:keyIcon="@drawable/sym_keyboard_space"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                            latin:keyWidth="30%p"
-                            latin:isModifier="true" />
+                            latin:keyStyle="spaceKeyStyle"
+                            latin:keyWidth="30%p" />
                         <Key
-                            latin:codes="@integer/key_tab"
-                            latin:keyIcon="@drawable/sym_keyboard_tab"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_tab"
-                            latin:isModifier="true" />
+                            latin:keyStyle="tabKeyStyle" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_space"
-                            latin:keyIcon="@drawable/sym_keyboard_space"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                            latin:keyWidth="30%p"
-                            latin:isModifier="true" />
+                            latin:keyStyle="spaceKeyStyle"
+                            latin:keyWidth="30%p" />
                     </default>
                 </switch>
                 <Key
                     latin:keyLabel="."
                     latin:keyHintIcon="@drawable/hint_popup"
                     latin:popupKeyboard="@xml/popup_punctuation"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
                     >
                         <Key
-                            latin:keyLabel=":-)"
-                            latin:keyOutputText=":-) "
-                            latin:keyHintIcon="@drawable/hint_popup"
-                            latin:popupKeyboard="@xml/popup_smileys"
+                            latin:keyStyle="smileyKeyStyle"
                             latin:keyWidth="25%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <case
                         latin:mode="web"
                     >
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="15%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="25%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </default>
                 </switch>
diff --git a/java/res/xml/kbd_qwerty_symbol.xml b/java/res/xml/kbd_qwerty_symbol.xml
deleted file mode 100644
index e90091c..0000000
--- a/java/res/xml/kbd_qwerty_symbol.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:hasSettingsKey="false"
-        >
-            <switch>
-                <!-- When this qwerty keyboard has no voice key but voice key is enabled, then
-                     symbol keyboard will have mic key. That means we should use "?123mic" key here.
-                     -->
-                <case
-                    latin:voiceKeyEnabled="true"
-                    latin:hasVoiceKey="false"
-                >
-                    <Key
-                        latin:codes="@integer/key_symbol"
-                        latin:keyIcon="@drawable/sym_keyboard_123_mic"
-                        latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
-                        latin:keyWidth="20%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </case>
-                <default>
-                    <Key
-                        latin:codes="@integer/key_symbol"
-                        latin:keyLabel="@string/label_symbol_key"
-                        latin:keyWidth="20%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </default>
-            </switch>
-        </case>
-        <case
-            latin:hasSettingsKey="true"
-        >
-            <switch>
-                <!-- When this qwerty keyboard has no voice key but voice key is enabled, then
-                     symbol keyboard will have mic key. That means we should use "?123mic" key here.
-                     -->
-                <case
-                    latin:voiceKeyEnabled="true"
-                    latin:hasVoiceKey="false"
-                >
-                    <Key
-                        latin:codes="@integer/key_symbol"
-                        latin:keyIcon="@drawable/sym_keyboard_123_mic"
-                        latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
-                        latin:keyWidth="15%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </case>
-                <default>
-                    <Key
-                        latin:codes="@integer/key_symbol"
-                        latin:keyLabel="@string/label_symbol_key"
-                        latin:keyWidth="15%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </default>
-            </switch>
-        </case>
-    </switch>
-</merge>
diff --git a/java/res/xml/kbd_symbols.xml b/java/res/xml/kbd_symbols.xml
index 55b7681..e9df816 100644
--- a/java/res/xml/kbd_symbols.xml
+++ b/java/res/xml/kbd_symbols.xml
@@ -25,6 +25,8 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:keyHeight="@dimen/key_height"
 >
+    <include
+        latin:keyboardLayout="@xml/kbd_functional_key_style" />
     <Row
         latin:rowEdgeFlags="top"
     >
@@ -105,11 +107,8 @@
     </Row>
     <Row>
         <Key
-            latin:codes="@integer/key_shift"
-            latin:keyLabel="@string/label_alt_key"
+            latin:keyStyle="altKeyStyle"
             latin:keyWidth="15%p"
-            latin:isModifier="true"
-            latin:isSticky="true"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="!"
@@ -134,12 +133,8 @@
             latin:popupKeyboard="@xml/kbd_popup_template"
             latin:popupCharacters="¿" />
         <Key
-            latin:codes="@integer/key_delete"
-            latin:keyIcon="@drawable/sym_keyboard_delete"
-            latin:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
-            latin:isModifier="true"
-            latin:isRepeatable="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <include latin:keyboardLayout="@xml/kbd_symbols_row4" />
diff --git a/java/res/xml/kbd_symbols_black_row4.xml b/java/res/xml/kbd_symbols_black_row4.xml
index a50f8c0..57f491b 100644
--- a/java/res/xml/kbd_symbols_black_row4.xml
+++ b/java/res/xml/kbd_symbols_black_row4.xml
@@ -29,8 +29,8 @@
                 latin:hasSettingsKey="false"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:codes="@integer/key_switch_alpha_symbol"
+                    latin:keyLabel="@string/label_to_alpha_key"
                     latin:keyWidth="20%p"
                     latin:keyEdgeFlags="left" />
                 <include
@@ -70,8 +70,8 @@
                 latin:hasSettingsKey="true"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:codes="@integer/key_switch_alpha_symbol"
+                    latin:keyLabel="@string/label_to_alpha_key"
                     latin:keyWidth="15%p"
                     latin:keyEdgeFlags="left" />
                 <Key
diff --git a/java/res/xml/kbd_symbols_f1.xml b/java/res/xml/kbd_symbols_f1.xml
index 39c411a..07ee4ed 100644
--- a/java/res/xml/kbd_symbols_f1.xml
+++ b/java/res/xml/kbd_symbols_f1.xml
@@ -26,12 +26,7 @@
             latin:hasVoiceKey="true"
         >
             <Key
-                latin:codes="@integer/key_voice"
-                latin:popupKeyboard="@xml/popup_mic"
-                latin:keyIcon="@drawable/sym_keyboard_mic"
-                latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:isModifier="true" />
+                latin:keyStyle="micKeyStyle" />
         </case>
         <case
             latin:hasVoiceKey="false"
@@ -40,7 +35,7 @@
                 latin:keyLabel=","
                 latin:popupKeyboard="@xml/popup_comma"
                 latin:keyHintIcon="@drawable/hint_popup"
-                latin:isModifier="true" />
+                latin:keyStyle="functionalKeyStyle" />
         </case>
     </switch>
 </merge>
diff --git a/java/res/xml/kbd_symbols_row4.xml b/java/res/xml/kbd_symbols_row4.xml
index f012a4d..1a5417d 100644
--- a/java/res/xml/kbd_symbols_row4.xml
+++ b/java/res/xml/kbd_symbols_row4.xml
@@ -29,44 +29,32 @@
                 latin:hasSettingsKey="false"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:keyStyle="toAlphaKeyStyle"
                     latin:keyWidth="20%p"
-                    latin:isModifier="true"
                     latin:keyEdgeFlags="left" />
                 <include
                     latin:keyboardLayout="@xml/kbd_symbols_f1" />
                 <Key
-                    latin:codes="@integer/key_space"
-                    latin:keyIcon="@drawable/sym_keyboard_space"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                    latin:keyWidth="40%p"
-                    latin:isModifier="true" />
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
                 <Key
                     latin:keyLabel="."
                     latin:keyHintIcon="@drawable/hint_popup"
                     latin:popupKeyboard="@xml/popup_punctuation"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
                     >
                         <Key
-                            latin:keyLabel=":-)"
-                            latin:keyOutputText=":-) "
-                            latin:keyHintIcon="@drawable/hint_popup"
-                            latin:popupKeyboard="@xml/popup_smileys"
+                            latin:keyStyle="smileyKeyStyle"
                             latin:keyWidth="20%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="20%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </default>
                 </switch>
@@ -75,49 +63,34 @@
                 latin:hasSettingsKey="true"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:keyStyle="toAlphaKeyStyle"
                     latin:keyWidth="15%p"
-                    latin:isModifier="true"
                     latin:keyEdgeFlags="left" />
                 <Key
-                    latin:codes="@integer/key_settings"
-                    latin:keyIcon="@drawable/sym_keyboard_settings"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_settings"
-                    latin:isModifier="true" />
+                    latin:keyStyle="settingsKeyStyle" />
                 <include
                     latin:keyboardLayout="@xml/kbd_symbols_f1" />
                 <Key
-                    latin:codes="@integer/key_space"
-                    latin:keyIcon="@drawable/sym_keyboard_space"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                    latin:keyWidth="30%p"
-                    latin:isModifier="true" />
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="30%p" />
                 <Key
                     latin:keyLabel="."
                     latin:keyHintIcon="@drawable/hint_popup"
                     latin:popupKeyboard="@xml/popup_punctuation"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
                     >
                         <Key
-                            latin:keyLabel=":-)"
-                            latin:keyOutputText=":-) "
-                            latin:keyHintIcon="@drawable/hint_popup"
-                            latin:popupKeyboard="@xml/popup_smileys"
+                            latin:keyStyle="smileyKeyStyle"
                             latin:keyWidth="25%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="25%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </default>
                 </switch>
diff --git a/java/res/xml/kbd_symbols_shift.xml b/java/res/xml/kbd_symbols_shift.xml
index 5279656..f3e79f7 100644
--- a/java/res/xml/kbd_symbols_shift.xml
+++ b/java/res/xml/kbd_symbols_shift.xml
@@ -25,6 +25,8 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:keyHeight="@dimen/key_height"
 >
+    <include
+        latin:keyboardLayout="@xml/kbd_functional_key_style" />
     <Row
         latin:rowEdgeFlags="top"
     >
@@ -58,7 +60,8 @@
     <Row>
         <Key
             latin:codes="@integer/key_tab"
-            latin:keyLabel="\u21E5"
+            latin:keyIcon="@drawable/sym_keyboard_tab"
+            latin:iconPreview="@drawable/sym_keyboard_feedback_tab"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="£" />
@@ -86,11 +89,8 @@
     </Row>
     <Row>
         <Key
-            latin:codes="@integer/key_shift"
-            latin:keyLabel="@string/label_alt_key"
+            latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
-            latin:isModifier="true"
-            latin:isSticky="true"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="™" />
@@ -113,12 +113,8 @@
             latin:popupKeyboard="@xml/kbd_popup_template"
             latin:popupCharacters="≥»›" />
         <Key
-            latin:codes="@integer/key_delete"
-            latin:keyIcon="@drawable/sym_keyboard_delete"
-            latin:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
-            latin:isModifier="true"
-            latin:isRepeatable="true"
             latin:keyEdgeFlags="right" />
     </Row>
     <include latin:keyboardLayout="@xml/kbd_symbols_shift_row4" />
diff --git a/java/res/xml/kbd_symbols_shift_black_row4.xml b/java/res/xml/kbd_symbols_shift_black_row4.xml
index 4e5ddcd..731cc6e 100644
--- a/java/res/xml/kbd_symbols_shift_black_row4.xml
+++ b/java/res/xml/kbd_symbols_shift_black_row4.xml
@@ -29,8 +29,8 @@
                 latin:hasSettingsKey="false"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:codes="@integer/key_switch_alpha_symbol"
+                    latin:keyLabel="@string/label_to_alpha_key"
                     latin:keyWidth="20%p"
                     latin:keyEdgeFlags="left" />
                 <Key
@@ -68,8 +68,8 @@
                 latin:hasSettingsKey="true"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:codes="@integer/key_switch_alpha_symbol"
+                    latin:keyLabel="@string/label_to_alpha_key"
                     latin:keyWidth="15%p"
                     latin:keyEdgeFlags="left" />
                 <Key
diff --git a/java/res/xml/kbd_symbols_shift_row4.xml b/java/res/xml/kbd_symbols_shift_row4.xml
index 170c8b9..9159bab 100644
--- a/java/res/xml/kbd_symbols_shift_row4.xml
+++ b/java/res/xml/kbd_symbols_shift_row4.xml
@@ -29,43 +29,31 @@
                 latin:hasSettingsKey="false"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:keyStyle="toAlphaKeyStyle"
                     latin:keyWidth="20%p"
-                    latin:isModifier="true"
                     latin:keyEdgeFlags="left" />
                 <Key
                     latin:keyLabel="„"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <Key
-                    latin:codes="@integer/key_space"
-                    latin:keyIcon="@drawable/sym_keyboard_space"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                    latin:keyWidth="40%p"
-                    latin:isModifier="true" />
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
                 <Key
                     latin:keyLabel="…"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
                     >
                         <Key
-                            latin:keyLabel=":-)"
-                            latin:keyOutputText=":-) "
-                            latin:keyHintIcon="@drawable/hint_popup"
-                            latin:popupKeyboard="@xml/popup_smileys"
+                            latin:keyStyle="smileyKeyStyle"
                             latin:keyWidth="20%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="20%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </default>
                 </switch>
@@ -74,48 +62,33 @@
                 latin:hasSettingsKey="true"
             >
                 <Key
-                    latin:codes="@integer/key_symbol"
-                    latin:keyLabel="@string/label_alpha_key"
+                    latin:keyStyle="toAlphaKeyStyle"
                     latin:keyWidth="15%p"
-                    latin:isModifier="true"
                     latin:keyEdgeFlags="left" />
                 <Key
-                    latin:codes="@integer/key_settings"
-                    latin:keyIcon="@drawable/sym_keyboard_settings"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_settings"
-                    latin:isModifier="true" />
+                    latin:keyStyle="settingsKeyStyle" />
                 <Key
                     latin:keyLabel="„"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <Key
-                    latin:codes="@integer/key_space"
-                    latin:keyIcon="@drawable/sym_keyboard_space"
-                    latin:iconPreview="@drawable/sym_keyboard_feedback_space"
-                    latin:keyWidth="30%p"
-                    latin:isModifier="true" />
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="30%p" />
                 <Key
                     latin:keyLabel="…"
-                    latin:isModifier="true" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
                     >
                         <Key
-                            latin:keyLabel=":-)"
-                            latin:keyOutputText=":-) "
-                            latin:keyHintIcon="@drawable/hint_popup"
-                            latin:popupKeyboard="@xml/popup_smileys"
+                            latin:keyStyle="smileyKeyStyle"
                             latin:keyWidth="25%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </case>
                     <default>
                         <Key
-                            latin:codes="@integer/key_return"
-                            latin:keyIcon="@drawable/sym_keyboard_return"
-                            latin:iconPreview="@drawable/sym_keyboard_feedback_return"
+                            latin:keyStyle="returnKeyStyle"
                             latin:keyWidth="25%p"
-                            latin:isModifier="true"
                             latin:keyEdgeFlags="right" />
                     </default>
                 </switch>
diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboard.java b/java/src/com/android/inputmethod/latin/BaseKeyboard.java
index cb0ac21..485cc31 100644
--- a/java/src/com/android/inputmethod/latin/BaseKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/BaseKeyboard.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.BaseKeyboardParser.ParseException;
+import com.android.inputmethod.latin.KeyStyles.KeyStyle;
 import com.android.inputmethod.latin.KeyboardSwitcher.KeyboardId;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -26,14 +28,11 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
-import android.util.Log;
-import android.util.TypedValue;
 import android.util.Xml;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.StringTokenizer;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -284,7 +283,8 @@
          * @param y the y coordinate of the top-left
          * @param parser the XML parser containing the attributes for this key
          */
-        public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
+        public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser,
+                KeyStyles keyStyles) {
             this(parent);
 
             TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
@@ -299,44 +299,47 @@
                     R.styleable.BaseKeyboard_keyWidth,
                     keyboard.mDisplayWidth, parent.defaultWidth) - gap;
             a.recycle();
+
             a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.BaseKeyboard_Key);
 
+            final KeyStyle style;
+            if (a.hasValue(R.styleable.BaseKeyboard_Key_keyStyle)) {
+                String styleName = a.getString(R.styleable.BaseKeyboard_Key_keyStyle);
+                style = keyStyles.getKeyStyle(styleName);
+                if (style == null)
+                    throw new ParseException("Unknown key style: " + styleName, parser);
+            } else {
+                style = keyStyles.getEmptyKeyStyle();
+            }
+
             // Horizontal gap is divided equally to both sides of the key.
             this.x = x + gap / 2;
             this.y = y;
 
-            TypedValue codesValue = new TypedValue();
-            a.getValue(R.styleable.BaseKeyboard_Key_codes, codesValue);
-            if (codesValue.type == TypedValue.TYPE_INT_DEC
-                    || codesValue.type == TypedValue.TYPE_INT_HEX) {
-                codes = new int[] { codesValue.data };
-            } else if (codesValue.type == TypedValue.TYPE_STRING) {
-                codes = parseCSV(codesValue.string.toString());
-            }
-
-            iconPreview = a.getDrawable(R.styleable.BaseKeyboard_Key_iconPreview);
+            codes = style.getIntArray(a, R.styleable.BaseKeyboard_Key_codes);
+            iconPreview = style.getDrawable(a, R.styleable.BaseKeyboard_Key_iconPreview);
             setDefaultBounds(iconPreview);
-            popupCharacters = a.getText(R.styleable.BaseKeyboard_Key_popupCharacters);
-            popupResId = a.getResourceId(R.styleable.BaseKeyboard_Key_popupKeyboard, 0);
-            repeatable = a.getBoolean(R.styleable.BaseKeyboard_Key_isRepeatable, false);
-            modifier = a.getBoolean(R.styleable.BaseKeyboard_Key_isModifier, false);
-            sticky = a.getBoolean(R.styleable.BaseKeyboard_Key_isSticky, false);
-            edgeFlags = a.getInt(R.styleable.BaseKeyboard_Key_keyEdgeFlags, 0);
+            popupCharacters = style.getText(a, R.styleable.BaseKeyboard_Key_popupCharacters);
+            popupResId = style.getResourceId(a, R.styleable.BaseKeyboard_Key_popupKeyboard, 0);
+            repeatable = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isRepeatable, false);
+            modifier = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isModifier, false);
+            sticky = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isSticky, false);
+            edgeFlags = style.getFlag(a, R.styleable.BaseKeyboard_Key_keyEdgeFlags, 0);
             edgeFlags |= parent.rowEdgeFlags;
 
-            icon = a.getDrawable(R.styleable.BaseKeyboard_Key_keyIcon);
+            icon = style.getDrawable(a, R.styleable.BaseKeyboard_Key_keyIcon);
             setDefaultBounds(icon);
-            hintIcon = a.getDrawable(R.styleable.BaseKeyboard_Key_keyHintIcon);
+            hintIcon = style.getDrawable(a, R.styleable.BaseKeyboard_Key_keyHintIcon);
             setDefaultBounds(hintIcon);
-            manualTemporaryUpperCaseHintIcon = a.getDrawable(
+            manualTemporaryUpperCaseHintIcon = style.getDrawable(a,
                     R.styleable.BaseKeyboard_Key_manualTemporaryUpperCaseHintIcon);
             setDefaultBounds(manualTemporaryUpperCaseHintIcon);
 
-            label = a.getText(R.styleable.BaseKeyboard_Key_keyLabel);
-            labelOption = a.getInt(R.styleable.BaseKeyboard_Key_keyLabelOption, 0);
-            manualTemporaryUpperCaseCode = a.getInt(
+            label = style.getText(a, R.styleable.BaseKeyboard_Key_keyLabel);
+            labelOption = style.getFlag(a, R.styleable.BaseKeyboard_Key_keyLabelOption, 0);
+            manualTemporaryUpperCaseCode = style.getInt(a,
                     R.styleable.BaseKeyboard_Key_manualTemporaryUpperCaseCode, 0);
-            text = a.getText(R.styleable.BaseKeyboard_Key_keyOutputText);
+            text = style.getText(a, R.styleable.BaseKeyboard_Key_keyOutputText);
 
             if (codes == null && !TextUtils.isEmpty(label)) {
                 codes = new int[] { label.charAt(0) };
@@ -366,28 +369,6 @@
             }
         }
 
-        private int[] parseCSV(String value) {
-            int count = 0;
-            int lastIndex = 0;
-            if (value.length() > 0) {
-                count++;
-                while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
-                    count++;
-                }
-            }
-            int[] values = new int[count];
-            count = 0;
-            StringTokenizer st = new StringTokenizer(value, ",");
-            while (st.hasMoreTokens()) {
-                try {
-                    values[count++] = Integer.parseInt(st.nextToken());
-                } catch (NumberFormatException nfe) {
-                    Log.e(TAG, "Error parsing keycodes " + value);
-                }
-            }
-            return values;
-        }
-
         /**
          * Detects if a point falls inside this key.
          * @param x the x-coordinate of the point
@@ -560,6 +541,10 @@
         mTotalHeight = y + mDefaultHeight;
     }
 
+    public KeyboardId getKeyboardId() {
+        return mId;
+    }
+
     public List<Key> getKeys() {
         return mKeys;
     }
@@ -688,14 +673,15 @@
 
     // TODO should be private
     protected BaseKeyboard.Key createKeyFromXml(Resources res, Row parent, int x, int y,
-            XmlResourceParser parser) {
-        return new BaseKeyboard.Key(res, parent, x, y, parser);
+            XmlResourceParser parser, KeyStyles keyStyles) {
+        return new BaseKeyboard.Key(res, parent, x, y, parser, keyStyles);
     }
 
     private void loadKeyboard(Context context, int xmlLayoutResId) {
         try {
-            BaseKeyboardParser parser = new BaseKeyboardParser(this, context.getResources());
-            parser.parseKeyboard(context.getResources().getXml(xmlLayoutResId));
+            final Resources res = context.getResources();
+            BaseKeyboardParser parser = new BaseKeyboardParser(this, res);
+            parser.parseKeyboard(res.getXml(xmlLayoutResId));
             // mTotalWidth is the width of this keyboard which is maximum width of row.
             mTotalWidth = parser.getMaxRowWidth();
             mTotalHeight = parser.getTotalHeight();
diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java
index 1aee2fc..ea209c5 100644
--- a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java
+++ b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java
@@ -83,27 +83,25 @@
  *     &gt;/default&lt;
  *   &gt;/switch&lt;
  * </pre>
- *
- * TODO: These are some random ideas to improve this parser.
- * - can specify keyWidth attribute by multiplication of default keyWidth
- *   for example: keyWidth="200%b" ("b" stands for "base")
- * - can declare style and specify styles within tags.
- *   for example:
+ * You can declare Key style and specify styles within Key tags.
+ * <pre>
  *     &gt;switch&lt;
  *       &gt;case colorScheme="white"&lt;
- *         &gt;declare-style name="shift-key" parentStyle="modifier-key"&lt;
- *           &gt;item name="keyIcon"&lt;@drawable/sym_keyboard_shift"&gt;/item&lt;
- *         &gt;/declare-style&lt;
+ *         &gt;key-style styleName="shift-key" parentStyle="modifier-key"
+ *           keyIcon="@drawable/sym_keyboard_shift"
+ *         /&lt;
  *       &gt;/case&lt;
  *       &gt;case colorScheme="black"&lt;
- *         &gt;declare-style name="shift-key" parentStyle="modifier-key"&lt;
- *           &gt;item name="keyIcon"&lt;@drawable/sym_bkeyboard_shift"&gt;/item&lt;
- *         &gt;/declare-style&lt;
+ *         &gt;key-style styleName="shift-key" parentStyle="modifier-key"
+ *           keyIcon="@drawable/sym_bkeyboard_shift"
+ *         /&lt;
  *       &gt;/case&lt;
  *     &gt;/switch&lt;
  *     ...
- *     &gt;Key include-style="shift-key" ... /&lt;
+ *     &gt;Key keyStyle="shift-key" ... /&lt;
+ * </pre>
  */
+
 public class BaseKeyboardParser {
     private static final String TAG = "BaseKeyboardParser";
     private static final boolean DEBUG_TAG = false;
@@ -118,6 +116,7 @@
     private static final String TAG_SWITCH = "switch";
     private static final String TAG_CASE = "case";
     private static final String TAG_DEFAULT = "default";
+    private static final String TAG_KEY_STYLE = "key-style";
 
     private final BaseKeyboard mKeyboard;
     private final Resources mResources;
@@ -127,6 +126,7 @@
     private int mMaxRowWidth = 0;
     private int mTotalHeight = 0;
     private Row mCurrentRow = null;
+    private final KeyStyles mKeyStyles = new KeyStyles();
 
     public BaseKeyboardParser(BaseKeyboard keyboard, Resources res) {
         mKeyboard = keyboard;
@@ -192,6 +192,8 @@
                     parseIncludeKeyboardContent(parser, keys);
                 } else if (TAG_SWITCH.equals(tag)) {
                     parseSwitchKeyboardContent(parser, keys);
+                } else if (TAG_KEY_STYLE.equals(tag)) {
+                    parseKeyStyle(parser, keys);
                 } else {
                     throw new IllegalStartTag(parser, TAG_ROW);
                 }
@@ -205,6 +207,8 @@
                     break;
                 } else if (TAG_MERGE.equals(tag)) {
                     break;
+                } else if (TAG_KEY_STYLE.equals(tag)) {
+                    continue;
                 } else {
                     throw new IllegalEndTag(parser, TAG_ROW);
                 }
@@ -227,6 +231,8 @@
                     parseIncludeRowContent(parser, row, keys);
                 } else if (TAG_SWITCH.equals(tag)) {
                     parseSwitchRowContent(parser, row, keys);
+                } else if (TAG_KEY_STYLE.equals(tag)) {
+                    parseKeyStyle(parser, keys);
                 } else {
                     throw new IllegalStartTag(parser, TAG_KEY);
                 }
@@ -241,6 +247,8 @@
                     break;
                 } else if (TAG_MERGE.equals(tag)) {
                     break;
+                } else if (TAG_KEY_STYLE.equals(tag)) {
+                    continue;
                 } else {
                     throw new IllegalEndTag(parser, TAG_KEY);
                 }
@@ -253,7 +261,8 @@
         if (keys == null) {
             checkEndTag(TAG_KEY, parser);
         } else {
-            Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser);
+            Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser,
+                    mKeyStyles);
             checkEndTag(TAG_KEY, parser);
             keys.add(key);
             if (key.codes[0] == BaseKeyboard.KEYCODE_SHIFT)
@@ -439,6 +448,24 @@
         return true;
     }
 
+    private void parseKeyStyle(XmlResourceParser parser, List<Key> keys)
+            throws XmlPullParserException, IOException {
+        TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.BaseKeyboard_KeyStyle);
+        TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.BaseKeyboard_Key);
+        try {
+            if (!a.hasValue(R.styleable.BaseKeyboard_KeyStyle_styleName))
+                throw new ParseException("<" + TAG_KEY_STYLE
+                        + "/> needs styleName attribute", parser);
+            if (keys != null)
+                mKeyStyles.parseKeyStyleAttributes(a, keyAttrs, parser);
+        } finally {
+            a.recycle();
+            keyAttrs.recycle();
+        }
+    }
+
     private static void checkEndTag(String tag, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         if (parser.next() == XmlResourceParser.END_TAG && tag.equals(parser.getName()))
@@ -486,7 +513,7 @@
     }
 
     @SuppressWarnings("serial")
-    private static class ParseException extends InflateException {
+    public static class ParseException extends InflateException {
         public ParseException(String msg, XmlResourceParser parser) {
             super(msg + " at line " + parser.getLineNumber());
         }
diff --git a/java/src/com/android/inputmethod/latin/KeyStyles.java b/java/src/com/android/inputmethod/latin/KeyStyles.java
new file mode 100644
index 0000000..e53e351
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/KeyStyles.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.BaseKeyboardParser.ParseException;
+
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+public class KeyStyles {
+    private static final String TAG = "KeyStyles";
+
+    private final HashMap<String, DeclaredKeyStyle> mStyles =
+            new HashMap<String, DeclaredKeyStyle>();
+    private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle();
+
+    public interface KeyStyle {
+        public int[] getIntArray(TypedArray a, int index);
+        public Drawable getDrawable(TypedArray a, int index);
+        public CharSequence getText(TypedArray a, int index);
+        public int getResourceId(TypedArray a, int index, int defaultValue);
+        public int getInt(TypedArray a, int index, int defaultValue);
+        public int getFlag(TypedArray a, int index, int defaultValue);
+        public boolean getBoolean(TypedArray a, int index, boolean defaultValue);
+    }
+
+    public static class EmptyKeyStyle implements KeyStyle {
+        private EmptyKeyStyle() {
+        }
+
+        public int[] getIntArray(TypedArray a, int index) {
+            return parseIntArray(a, index);
+        }
+
+        public Drawable getDrawable(TypedArray a, int index) {
+            return a.getDrawable(index);
+        }
+
+        public CharSequence getText(TypedArray a, int index) {
+            return a.getText(index);
+        }
+
+        public int getResourceId(TypedArray a, int index, int defaultValue) {
+            return a.getResourceId(index, defaultValue);
+        }
+
+        public int getInt(TypedArray a, int index, int defaultValue) {
+            return a.getInt(index, defaultValue);
+        }
+
+        public int getFlag(TypedArray a, int index, int defaultValue) {
+            return a.getInt(index, defaultValue);
+        }
+
+        public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
+            return a.getBoolean(index, defaultValue);
+        }
+
+        protected static int[] parseIntArray(TypedArray a, int index) {
+            TypedValue v = new TypedValue();
+            a.getValue(index, v);
+            if (v.type == TypedValue.TYPE_INT_DEC || v.type == TypedValue.TYPE_INT_HEX) {
+                return new int[] { v.data };
+            } else if (v.type == TypedValue.TYPE_STRING) {
+                return parseCSV(v.string.toString());
+            } else {
+                return null;
+            }
+        }
+
+        private static int[] parseCSV(String value) {
+            int count = 0;
+            int lastIndex = 0;
+            if (value.length() > 0) {
+                count++;
+                while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
+                    count++;
+                }
+            }
+            int[] values = new int[count];
+            count = 0;
+            StringTokenizer st = new StringTokenizer(value, ",");
+            while (st.hasMoreTokens()) {
+                try {
+                    values[count++] = Integer.parseInt(st.nextToken());
+                } catch (NumberFormatException nfe) {
+                    Log.e(TAG, "Error parsing integer CSV " + value);
+                }
+            }
+            return values;
+        }
+    }
+
+    public static class DeclaredKeyStyle extends EmptyKeyStyle {
+        private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>();
+
+        @Override
+        public int[] getIntArray(TypedArray a, int index) {
+            return a.hasValue(index)
+                    ? super.getIntArray(a, index) : (int[])mAttributes.get(index);
+        }
+
+        @Override
+        public Drawable getDrawable(TypedArray a, int index) {
+            return a.hasValue(index)
+                    ? super.getDrawable(a, index) : (Drawable)mAttributes.get(index);
+        }
+
+        @Override
+        public CharSequence getText(TypedArray a, int index) {
+            return a.hasValue(index)
+                    ? super.getText(a, index) : (CharSequence)mAttributes.get(index);
+        }
+
+        @Override
+        public int getResourceId(TypedArray a, int index, int defaultValue) {
+            final Integer value = (Integer)mAttributes.get(index);
+            return super.getResourceId(a, index, (value != null) ? value : defaultValue);
+        }
+
+        @Override
+        public int getFlag(TypedArray a, int index, int defaultValue) {
+            final Integer value = (Integer)mAttributes.get(index);
+            return super.getFlag(a, index, defaultValue) | (value != null ? value : 0);
+        }
+
+        @Override
+        public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
+            final Boolean value = (Boolean)mAttributes.get(index);
+            return super.getBoolean(a, index, (value != null) ? value : defaultValue);
+        }
+
+        private DeclaredKeyStyle() {
+            super();
+        }
+
+        private void parseKeyStyleAttributes(TypedArray a) {
+            // TODO: Currently not all Key attributes can be declared as style.
+            readIntArray(a, R.styleable.BaseKeyboard_Key_codes);
+            readText(a, R.styleable.BaseKeyboard_Key_keyLabel);
+            readFlag(a, R.styleable.BaseKeyboard_Key_keyLabelOption);
+            readText(a, R.styleable.BaseKeyboard_Key_keyOutputText);
+            readDrawable(a, R.styleable.BaseKeyboard_Key_keyIcon);
+            readDrawable(a, R.styleable.BaseKeyboard_Key_iconPreview);
+            readDrawable(a, R.styleable.BaseKeyboard_Key_keyHintIcon);
+            readResourceId(a, R.styleable.BaseKeyboard_Key_popupKeyboard);
+            readBoolean(a, R.styleable.BaseKeyboard_Key_isModifier);
+            readBoolean(a, R.styleable.BaseKeyboard_Key_isSticky);
+            readBoolean(a, R.styleable.BaseKeyboard_Key_isRepeatable);
+        }
+
+        private void readDrawable(TypedArray a, int index) {
+            if (a.hasValue(index))
+                mAttributes.put(index, a.getDrawable(index));
+        }
+
+        private void readText(TypedArray a, int index) {
+            if (a.hasValue(index))
+                mAttributes.put(index, a.getText(index));
+        }
+
+        private void readResourceId(TypedArray a, int index) {
+            if (a.hasValue(index))
+                mAttributes.put(index, a.getResourceId(index, 0));
+        }
+
+        private void readFlag(TypedArray a, int index) {
+            final Integer value = (Integer)mAttributes.get(index);
+            if (a.hasValue(index))
+                mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
+        }
+
+        private void readBoolean(TypedArray a, int index) {
+            if (a.hasValue(index))
+                mAttributes.put(index, a.getBoolean(index, false));
+        }
+
+        private void readIntArray(TypedArray a, int index) {
+            if (a.hasValue(index)) {
+                final int[] value = parseIntArray(a, index);
+                if (value != null)
+                    mAttributes.put(index, value);
+            }
+        }
+
+        private void addParent(DeclaredKeyStyle parentStyle) {
+            mAttributes.putAll(parentStyle.mAttributes);
+        }
+    }
+
+    public void parseKeyStyleAttributes(TypedArray a, TypedArray keyAttrs,
+            XmlResourceParser parser) {
+        String styleName = a.getString(R.styleable.BaseKeyboard_KeyStyle_styleName);
+        if (mStyles.containsKey(styleName))
+            throw new ParseException("duplicate key style declared: " + styleName, parser);
+
+        final DeclaredKeyStyle style = new DeclaredKeyStyle();
+        if (a.hasValue(R.styleable.BaseKeyboard_KeyStyle_parentStyle)) {
+            String parentStyle = a.getString(
+                    R.styleable.BaseKeyboard_KeyStyle_parentStyle);
+            final DeclaredKeyStyle parent = mStyles.get(parentStyle);
+            if (parent == null)
+                throw new ParseException("Unknown parentStyle " + parent, parser);
+            style.addParent(parent);
+        }
+        style.parseKeyStyleAttributes(keyAttrs);
+        mStyles.put(styleName, style);
+    }
+
+    public KeyStyle getKeyStyle(String styleName) {
+        return mStyles.get(styleName);
+    }
+
+    public KeyStyle getEmptyKeyStyle() {
+        return EMPTY_KEY_STYLE;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9c7af35..d624ae4 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -17,8 +17,7 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.latin.LatinIMEUtil.RingCharBuffer;
-import com.android.inputmethod.voice.FieldContext;
-import com.android.inputmethod.voice.VoiceInput;
+import com.android.inputmethod.voice.VoiceIMEConnector;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -41,8 +40,6 @@
 import android.os.Vibrator;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceManager;
-import android.speech.SpeechRecognizer;
-import android.text.ClipboardManager;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -50,10 +47,7 @@
 import android.util.Printer;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
@@ -70,25 +64,20 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 /**
  * Input method implementation for Qwerty'ish keyboard.
  */
 public class LatinIME extends InputMethodService
         implements BaseKeyboardView.OnKeyboardActionListener,
-        VoiceInput.UiListener,
         SharedPreferences.OnSharedPreferenceChangeListener,
         Tutorial.TutorialListener {
     private static final String TAG = "LatinIME";
     private static final boolean PERF_DEBUG = false;
-    static final boolean DEBUG = false;
-    static final boolean TRACE = false;
-    static final boolean VOICE_INSTALLED = true;
-    static final boolean ENABLE_VOICE_BUTTON = true;
+    private static final boolean DEBUG = false;
+    private static final boolean TRACE = false;
 
     private static final String PREF_SOUND_ON = "sound_on";
     private static final String PREF_POPUP_ON = "popup_on";
@@ -97,21 +86,6 @@
     private static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
     private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
     private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
-    private static final String PREF_VOICE_MODE = "voice_mode";
-
-    // Whether or not the user has used voice input before (and thus, whether to show the
-    // first-run warning dialog or not).
-    private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
-
-    // Whether or not the user has used voice input from an unsupported locale UI before.
-    // For example, the user has a Chinese UI but activates voice input.
-    private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
-            "has_used_voice_input_unsupported_locale";
-
-    // The private IME option used to indicate that no microphone should be shown for a
-    // given text field. For instance this is specified by the search dialog when the
-    // dialog is already showing a voice search button.
-    private static final String IME_OPTION_NO_MICROPHONE = "nm";
 
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
     public static final String PREF_INPUT_LANGUAGE = "input_language";
@@ -156,29 +130,22 @@
     private CompletionInfo[] mCompletions;
 
     private AlertDialog mOptionsDialog;
-    private AlertDialog mVoiceWarningDialog;
 
     private KeyboardSwitcher mKeyboardSwitcher;
     private SubtypeSwitcher mSubtypeSwitcher;
+    private VoiceIMEConnector mVoiceConnector;
 
     private UserDictionary mUserDictionary;
     private UserBigramDictionary mUserBigramDictionary;
     private ContactsDictionary mContactsDictionary;
     private AutoDictionary mAutoDictionary;
 
-    private Hints mHints;
-
     private Resources mResources;
 
     private final StringBuilder mComposing = new StringBuilder();
     private WordComposer mWord = new WordComposer();
     private int mCommittedLength;
     private boolean mPredicting;
-    private boolean mRecognizing;
-    private boolean mAfterVoiceInput;
-    private boolean mImmediatelyAfterVoiceInput;
-    private boolean mShowingVoiceSuggestions;
-    private boolean mVoiceInputHighlighted;
     private CharSequence mBestWord;
     private boolean mPredictionOn;
     private boolean mCompletionOn;
@@ -189,19 +156,13 @@
     private boolean mReCorrectionEnabled;
     private boolean mBigramSuggestionEnabled;
     private boolean mAutoCorrectOn;
-    private boolean mPasswordText;
     private boolean mVibrateOn;
     private boolean mSoundOn;
     private boolean mPopupOn;
     private boolean mAutoCap;
     private boolean mQuickFixes;
-    private boolean mHasUsedVoiceInput;
-    private boolean mHasUsedVoiceInputUnsupportedLocale;
-    private boolean mLocaleSupportedForVoiceInput;
-    private boolean mIsShowingHint;
     private int     mCorrectionMode;
-    private boolean mVoiceKeyEnabled;
-    private boolean mVoiceButtonOnPrimary;
+
     private int     mOrientation;
     private List<CharSequence> mSuggestPuncList;
     // Keep track of the last selection range to decide if we need to show word alternatives
@@ -227,25 +188,15 @@
     /* package */ String mWordSeparators;
     private String mSentenceSeparators;
     private String mSuggestPuncs;
-    private VoiceInput mVoiceInput;
-    private final VoiceResults mVoiceResults = new VoiceResults();
+    // TODO: Move this flag to VoiceIMEConnector
     private boolean mConfigurationChanging;
 
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
     private boolean mRefreshKeyboardRequired;
 
-    // For each word, a list of potential replacements, usually from voice.
-    private final Map<String, List<CharSequence>> mWordToSuggestions =
-            new HashMap<String, List<CharSequence>>();
-
     private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
 
-    private class VoiceResults {
-        List<String> candidates;
-        Map<String, List<CharSequence>> alternatives;
-    }
-
     public abstract static class WordAlternatives {
         protected CharSequence mChosenWord;
 
@@ -294,9 +245,9 @@
         }
     }
 
-    /* package */ UIHandler mHandler = new UIHandler();
+    /* package */ final UIHandler mHandler = new UIHandler();
 
-    /* package */ class UIHandler extends Handler {
+    public class UIHandler extends Handler {
         private static final int MSG_UPDATE_SUGGESTIONS = 0;
         private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1;
         private static final int MSG_UPDATE_SHIFT_STATE = 2;
@@ -316,7 +267,9 @@
                 mKeyboardSwitcher.updateShiftState();
                 break;
             case MSG_VOICE_RESULTS:
-                handleVoiceResults();
+                mVoiceConnector.handleVoiceResults(mKeyboardSwitcher, preferCapitalization()
+                        || (mKeyboardSwitcher.isAlphabetMode()
+                                && mKeyboardSwitcher.isShiftedOrShiftLocked()));
                 break;
             case MSG_START_TUTORIAL:
                 if (mTutorial == null) {
@@ -405,19 +358,7 @@
         // register to receive ringer mode changes for silent mode
         IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
-        if (VOICE_INSTALLED) {
-            mVoiceInput = new VoiceInput(this, this);
-            mHints = new Hints(this, new Hints.Display() {
-                public void showHint(int viewResource) {
-                    LayoutInflater inflater = (LayoutInflater) getSystemService(
-                            Context.LAYOUT_INFLATER_SERVICE);
-                    View view = inflater.inflate(viewResource, null);
-                    setCandidatesView(view);
-                    setCandidatesViewShown(true);
-                    mIsShowingHint = true;
-                }
-              });
-        }
+        mVoiceConnector = VoiceIMEConnector.init(this, mHandler);
         prefs.registerOnSharedPreferenceChangeListener(this);
     }
 
@@ -508,9 +449,7 @@
             mContactsDictionary.close();
         }
         unregisterReceiver(mReceiver);
-        if (VOICE_INSTALLED && mVoiceInput != null) {
-            mVoiceInput.destroy();
-        }
+        mVoiceConnector.destroy();
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -531,15 +470,14 @@
             final int mode = mKeyboardSwitcher.getKeyboardMode();
             final EditorInfo attribute = getCurrentInputEditorInfo();
             final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
-            mKeyboardSwitcher.loadKeyboard(mode, imeOptions, mVoiceKeyEnabled,
-                    mVoiceButtonOnPrimary);
+            mKeyboardSwitcher.loadKeyboard(mode, imeOptions,
+                    mVoiceConnector.isVoiceButtonEnabled(),
+                    mVoiceConnector.isVoiceButtonOnPrimary());
         }
 
         mConfigurationChanging = true;
         super.onConfigurationChanged(conf);
-        if (mRecognizing) {
-            switchToRecognitionStatusView();
-        }
+        mVoiceConnector.onConfigurationChanged(mConfigurationChanging);
         mConfigurationChanging = false;
     }
 
@@ -591,16 +529,8 @@
         // Most such things we decide below in the switch statement, but we need to know
         // now whether this is a password text field, because we need to know now (before
         // the switch statement) whether we want to enable the voice button.
-        mPasswordText = false;
         int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
-        if (isPasswordVariation(variation)) {
-            mPasswordText = true;
-        }
-
-        mAfterVoiceInput = false;
-        mImmediatelyAfterVoiceInput = false;
-        mShowingVoiceSuggestions = false;
-        mVoiceInputHighlighted = false;
+        mVoiceConnector.resetVoiceStates(isPasswordVariation(variation));
         mInputTypeNoAutoCorrect = false;
         mPredictionOn = false;
         mCompletionOn = false;
@@ -679,7 +609,8 @@
         mJustAddedAutoSpace = false;
 
         loadSettings(attribute);
-        switcher.loadKeyboard(mode, attribute.imeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary);
+        switcher.loadKeyboard(mode, attribute.imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
+                mVoiceConnector.isVoiceButtonOnPrimary());
         switcher.updateShiftState();
 
         setCandidatesViewShownInternal(isCandidateStripVisible(),
@@ -731,14 +662,8 @@
         LatinImeLogger.commit();
         onAutoCompletionStateChanged(false);
 
-        if (VOICE_INSTALLED && !mConfigurationChanging) {
-            if (mAfterVoiceInput) {
-                mVoiceInput.flushAllTextModificationCounters();
-                mVoiceInput.logInputEnded();
-            }
-            mVoiceInput.flushLogs();
-            mVoiceInput.cancel();
-        }
+        mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+
         BaseKeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (inputView != null)
             inputView.closing();
@@ -760,13 +685,7 @@
     @Override
     public void onUpdateExtractedText(int token, ExtractedText text) {
         super.onUpdateExtractedText(token, text);
-        InputConnection ic = getCurrentInputConnection();
-        if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
-            if (mHints.showPunctuationHintIfNecessary(ic)) {
-                mVoiceInput.logPunctuationHintDisplayed();
-            }
-        }
-        mImmediatelyAfterVoiceInput = false;
+        mVoiceConnector.showPunctuationHintIfNecessary();
     }
 
     @Override
@@ -785,14 +704,12 @@
                     + ", ce=" + candidatesEnd);
         }
 
-        if (mAfterVoiceInput) {
-            mVoiceInput.setCursorPos(newSelEnd);
-            mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
-        }
+        mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
 
         // If the current selection in the text view changes, we should
         // clear whatever candidate text we have.
-        if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
+        if ((((mComposing.length() > 0 && mPredicting)
+                || mVoiceConnector.isVoiceInputHighlighted())
                 && (newSelStart != candidatesEnd
                     || newSelEnd != candidatesEnd)
                 && mLastSelectionStart != newSelStart)) {
@@ -804,7 +721,7 @@
             if (ic != null) {
                 ic.finishComposingText();
             }
-            mVoiceInputHighlighted = false;
+            mVoiceConnector.setVoiceInputHighlighted(false);
         } else if (!mPredicting && !mJustAccepted) {
             switch (TextEntryState.getState()) {
                 case ACCEPTED_DEFAULT:
@@ -829,8 +746,7 @@
                 if (isPredictionOn() && !mJustReverted
                         && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
                                 || TextEntryState.isCorrecting())
-                                && (newSelStart < newSelEnd - 1 || (!mPredicting))
-                                && !mVoiceInputHighlighted) {
+                                && (newSelStart < newSelEnd - 1 || (!mPredicting))) {
                     if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
                         mHandler.postUpdateOldSuggestions();
                     } else {
@@ -888,18 +804,7 @@
             mOptionsDialog.dismiss();
             mOptionsDialog = null;
         }
-        if (!mConfigurationChanging) {
-            if (mAfterVoiceInput) mVoiceInput.logInputEnded();
-            if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
-                mVoiceInput.logKeyboardWarningDialogDismissed();
-                mVoiceWarningDialog.dismiss();
-                mVoiceWarningDialog = null;
-            }
-            if (VOICE_INSTALLED & mRecognizing) {
-                mVoiceInput.cancel();
-            }
-        }
-        mWordToSuggestions.clear();
+        mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
         mWordHistory.clear();
         super.hideWindow();
         TextEntryState.endSession();
@@ -1019,21 +924,7 @@
         return super.onKeyUp(keyCode, event);
     }
 
-    private void revertVoiceInput() {
-        InputConnection ic = getCurrentInputConnection();
-        if (ic != null) ic.commitText("", 1);
-        updateSuggestions();
-        mVoiceInputHighlighted = false;
-    }
-
-    private void commitVoiceInput() {
-        InputConnection ic = getCurrentInputConnection();
-        if (ic != null) ic.finishComposingText();
-        updateSuggestions();
-        mVoiceInputHighlighted = false;
-    }
-
-    private void commitTyped(InputConnection inputConnection) {
+    public void commitTyped(InputConnection inputConnection) {
         if (mPredicting) {
             mPredicting = false;
             if (mComposing.length() > 0) {
@@ -1222,10 +1113,9 @@
         case LatinKeyboardView.KEYCODE_CAPSLOCK:
             switcher.toggleCapsLock();
             break;
-        case LatinKeyboardView.KEYCODE_VOICE:
-            if (VOICE_INSTALLED) {
-                startListening(false /* was a button press, was not a swipe */);
-            }
+        case LatinKeyboardView.KEYCODE_VOICE: /* was a button press, was not a swipe */
+            mVoiceConnector.startListening(false,
+                    mKeyboardSwitcher.getInputView().getWindowToken(), mConfigurationChanging);
             break;
         case KEYCODE_TAB:
             handleTab();
@@ -1250,9 +1140,7 @@
     }
 
     public void onText(CharSequence text) {
-        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
-            commitVoiceInput();
-        }
+        mVoiceConnector.commitVoiceInput();
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         abortCorrection(false);
@@ -1274,29 +1162,14 @@
     }
 
     private void handleBackspace() {
-        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
-            mVoiceInput.incrementTextModificationDeleteCount(
-                    mVoiceResults.candidates.get(0).toString().length());
-            revertVoiceInput();
-            return;
-        }
+        if (mVoiceConnector.logAndRevertVoiceInput()) return;
         boolean deleteChar = false;
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
 
         ic.beginBatchEdit();
 
-        if (mAfterVoiceInput) {
-            // Don't log delete if the user is pressing delete at
-            // the beginning of the text box (hence not deleting anything)
-            if (mVoiceInput.getCursorPos() > 0) {
-                // If anything was selected before the delete was pressed, increment the
-                // delete count by the length of the selection
-                int deleteLen  =  mVoiceInput.getSelectionSpan() > 0 ?
-                        mVoiceInput.getSelectionSpan() : 1;
-                mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
-            }
-        }
+        mVoiceConnector.handleBackspace();
 
         if (mPredicting) {
             final int length = mComposing.length();
@@ -1377,14 +1250,8 @@
     }
 
     private void handleCharacter(int primaryCode, int[] keyCodes) {
-        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
-            commitVoiceInput();
-        }
+        mVoiceConnector.handleCharacter();
 
-        if (mAfterVoiceInput) {
-            // Assume input length is 1. This assumption fails for smiley face insertions.
-            mVoiceInput.incrementTextModificationInsertCount(1);
-        }
         if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
             abortCorrection(false);
         }
@@ -1441,14 +1308,7 @@
     }
 
     private void handleSeparator(int primaryCode) {
-        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
-            commitVoiceInput();
-        }
-
-        if (mAfterVoiceInput){
-            // Assume input length is 1. This assumption fails for smiley face insertions.
-            mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
-        }
+        mVoiceConnector.handleSeparator();
 
         // Should dismiss the "Touch again to save" message when handling separator
         if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
@@ -1509,9 +1369,7 @@
 
     private void handleClose() {
         commitTyped(getCurrentInputConnection());
-        if (VOICE_INSTALLED & mRecognizing) {
-            mVoiceInput.cancel();
-        }
+        mVoiceConnector.handleClose();
         requestHideSelf(0);
         LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (inputView != null)
@@ -1557,16 +1415,9 @@
                 && (isPredictionOn() || mCompletionOn || isShowingPunctuationList()));
     }
 
-    public void onCancelVoice() {
-        if (mRecognizing) {
-            switchToKeyboardView();
-        }
-    }
-
-    private void switchToKeyboardView() {
+    public void switchToKeyboardView() {
       mHandler.post(new Runnable() {
           public void run() {
-              mRecognizing = false;
               if (mKeyboardSwitcher.getInputView() != null) {
                   setInputView(mKeyboardSwitcher.getInputView());
               }
@@ -1576,175 +1427,18 @@
           }});
     }
 
-    private void switchToRecognitionStatusView() {
-        final boolean configChanged = mConfigurationChanging;
-        mHandler.post(new Runnable() {
-            public void run() {
-                setCandidatesViewShown(false);
-                mRecognizing = true;
-                View v = mVoiceInput.getView();
-                ViewParent p = v.getParent();
-                if (p != null && p instanceof ViewGroup) {
-                    ((ViewGroup)v.getParent()).removeView(v);
-                }
-                setInputView(v);
-                updateInputViewShown();
-                if (configChanged) {
-                    mVoiceInput.onConfigurationChanged();
-                }
-        }});
-    }
-
-    private void startListening(boolean swipe) {
-        if (!mHasUsedVoiceInput ||
-                (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
-            // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
-            showVoiceWarningDialog(swipe);
-        } else {
-            reallyStartListening(swipe);
-        }
-    }
-
-    private void reallyStartListening(boolean swipe) {
-        if (!mHasUsedVoiceInput) {
-            // The user has started a voice input, so remember that in the
-            // future (so we don't show the warning dialog after the first run).
-            SharedPreferences.Editor editor =
-                    PreferenceManager.getDefaultSharedPreferences(this).edit();
-            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
-            SharedPreferencesCompat.apply(editor);
-            mHasUsedVoiceInput = true;
-        }
-
-        if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
-            // The user has started a voice input from an unsupported locale, so remember that
-            // in the future (so we don't show the warning dialog the next time they do this).
-            SharedPreferences.Editor editor =
-                    PreferenceManager.getDefaultSharedPreferences(this).edit();
-            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
-            SharedPreferencesCompat.apply(editor);
-            mHasUsedVoiceInputUnsupportedLocale = true;
-        }
-
-        // Clear N-best suggestions
-        clearSuggestions();
-
-        FieldContext context = makeFieldContext();
-        mVoiceInput.startListening(context, swipe);
-        switchToRecognitionStatusView();
-    }
-
-    private void showVoiceWarningDialog(final boolean swipe) {
-        AlertDialog.Builder builder = new AlertDialog.Builder(this);
-        builder.setCancelable(true);
-        builder.setIcon(R.drawable.ic_mic_dialog);
-        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int whichButton) {
-                mVoiceInput.logKeyboardWarningDialogOk();
-                reallyStartListening(swipe);
-            }
-        });
-        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int whichButton) {
-                mVoiceInput.logKeyboardWarningDialogCancel();
-            }
-        });
-
-        if (mLocaleSupportedForVoiceInput) {
-            String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
-                    getString(R.string.voice_warning_how_to_turn_off);
-            builder.setMessage(message);
-        } else {
-            String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" +
-                    getString(R.string.voice_warning_may_not_understand) + "\n\n" +
-                    getString(R.string.voice_warning_how_to_turn_off);
-            builder.setMessage(message);
-        }
-
-        builder.setTitle(R.string.voice_warning_title);
-        mVoiceWarningDialog = builder.create();
-
-        Window window = mVoiceWarningDialog.getWindow();
-        WindowManager.LayoutParams lp = window.getAttributes();
-        lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
-        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-        window.setAttributes(lp);
-        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-        mVoiceInput.logKeyboardWarningDialogShown();
-        mVoiceWarningDialog.show();
-    }
-
-    public void onVoiceResults(List<String> candidates,
-            Map<String, List<CharSequence>> alternatives) {
-        if (!mRecognizing) {
-            return;
-        }
-        mVoiceResults.candidates = candidates;
-        mVoiceResults.alternatives = alternatives;
-        mHandler.updateVoiceResults();
-    }
-
-    private void handleVoiceResults() {
-        mAfterVoiceInput = true;
-        mImmediatelyAfterVoiceInput = true;
-
-        InputConnection ic = getCurrentInputConnection();
-        if (!isFullscreenMode()) {
-            // Start listening for updates to the text from typing, etc.
-            if (ic != null) {
-                ExtractedTextRequest req = new ExtractedTextRequest();
-                ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
-            }
-        }
-
-        vibrate();
-        switchToKeyboardView();
-
-        final List<CharSequence> nBest = new ArrayList<CharSequence>();
-        KeyboardSwitcher switcher = mKeyboardSwitcher;
-        boolean capitalizeFirstWord = preferCapitalization()
-                || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked());
-        for (String c : mVoiceResults.candidates) {
-            if (capitalizeFirstWord) {
-                c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
-            }
-            nBest.add(c);
-        }
-
-        if (nBest.size() == 0) {
-            return;
-        }
-
-        String bestResult = nBest.get(0).toString();
-
-        mVoiceInput.logVoiceInputDelivered(bestResult.length());
-
-        mHints.registerVoiceResult(bestResult);
-
-        if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
-
-        commitTyped(ic);
-        EditingUtil.appendText(ic, bestResult);
-
-        if (ic != null) ic.endBatchEdit();
-
-        mVoiceInputHighlighted = true;
-        mWordToSuggestions.putAll(mVoiceResults.alternatives);
-    }
-
-    private void clearSuggestions() {
+    public void clearSuggestions() {
         setSuggestions(null, false, false, false);
     }
 
-    private void setSuggestions(
+    public void setSuggestions(
             List<CharSequence> suggestions,
             boolean completions,
             boolean typedWordValid,
             boolean haveMinimalSuggestion) {
 
-        if (mIsShowingHint) {
+        if (mVoiceConnector.getAndResetIsShowingHint()) {
              setCandidatesView(mCandidateViewContainer);
-             mIsShowingHint = false;
         }
 
         if (mCandidateView != null) {
@@ -1753,11 +1447,12 @@
         }
     }
 
-    private void updateSuggestions() {
+    public void updateSuggestions() {
         mKeyboardSwitcher.setPreferredLetters(null);
 
         // Check if we have a suggestion engine attached.
-        if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
+        if ((mSuggest == null || !isPredictionOn())
+                && !mVoiceConnector.isVoiceInputHighlighted()) {
             return;
         }
 
@@ -1846,13 +1541,7 @@
 
     public void pickSuggestionManually(int index, CharSequence suggestion) {
         List<CharSequence> suggestions = mCandidateView.getSuggestions();
-        if (mAfterVoiceInput && mShowingVoiceSuggestions) {
-            mVoiceInput.flushAllTextModificationCounters();
-            // send this intent AFTER logging any prior aggregated edits.
-            mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
-                                                          mWordSeparators,
-                                                          getCurrentInputConnection());
-        }
+        mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
 
         final boolean correcting = TextEntryState.isCorrecting();
         InputConnection ic = getCurrentInputConnection();
@@ -1932,27 +1621,6 @@
         }
     }
 
-    private void rememberReplacedWord(CharSequence suggestion) {
-        if (mShowingVoiceSuggestions) {
-            // Retain the replaced word in the alternatives array.
-            EditingUtil.Range range = new EditingUtil.Range();
-            String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
-                    mWordSeparators, range);
-            if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
-                wordToBeReplaced = wordToBeReplaced.toLowerCase();
-            }
-            if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
-                List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
-                if (suggestions.contains(suggestion)) {
-                    suggestions.remove(suggestion);
-                }
-                suggestions.add(wordToBeReplaced);
-                mWordToSuggestions.remove(wordToBeReplaced);
-                mWordToSuggestions.put(suggestion.toString(), suggestions);
-            }
-        }
-    }
-
     /**
      * Commits the chosen word to the text field and saves it for later
      * retrieval.
@@ -1967,7 +1635,7 @@
             return;
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
-            rememberReplacedWord(suggestion);
+            mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
             ic.commitText(suggestion, 1);
         }
         saveWordInHistory(suggestion);
@@ -1982,38 +1650,6 @@
     }
 
     /**
-     * Tries to apply any voice alternatives for the word if this was a spoken word and
-     * there are voice alternatives.
-     * @param touching The word that the cursor is touching, with position information
-     * @return true if an alternative was found, false otherwise.
-     */
-    private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
-        // Search for result in spoken word alternatives
-        String selectedWord = touching.word.toString().trim();
-        if (!mWordToSuggestions.containsKey(selectedWord)) {
-            selectedWord = selectedWord.toLowerCase();
-        }
-        if (mWordToSuggestions.containsKey(selectedWord)) {
-            mShowingVoiceSuggestions = true;
-            List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
-            // If the first letter of touching is capitalized, make all the suggestions
-            // start with a capital letter.
-            if (Character.isUpperCase(touching.word.charAt(0))) {
-                for (int i = 0; i < suggestions.size(); i++) {
-                    String origSugg = (String) suggestions.get(i);
-                    String capsSugg = origSugg.toUpperCase().charAt(0)
-                            + origSugg.subSequence(1, origSugg.length()).toString();
-                    suggestions.set(i, capsSugg);
-                }
-            }
-            setSuggestions(suggestions, false, true, true);
-            setCandidatesViewShown(true);
-            return true;
-        }
-        return false;
-    }
-
-    /**
      * Tries to apply any typed alternatives for the word if we have any cached alternatives,
      * otherwise tries to find new corrections and completions for the word.
      * @param touching The word that the cursor is touching, with position information
@@ -2061,7 +1697,7 @@
     }
 
     private void setOldSuggestions() {
-        mShowingVoiceSuggestions = false;
+        mVoiceConnector.setShowingVoiceSuggestions(false);
         if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
             return;
         }
@@ -2075,7 +1711,8 @@
             if (touching != null && touching.word.length() > 1) {
                 ic.beginBatchEdit();
 
-                if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
+                if (!mVoiceConnector.applyVoiceAlternatives(touching)
+                        && !applyTypedAlternatives(touching)) {
                     abortCorrection(true);
                 } else {
                     TextEntryState.selectedForCorrection();
@@ -2221,8 +1858,8 @@
         final int mode = switcher.getKeyboardMode();
         final EditorInfo attribute = getCurrentInputEditorInfo();
         final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
-        switcher.loadKeyboard(mode, imeOptions, mVoiceKeyEnabled,
-                mVoiceButtonOnPrimary);
+        switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
+                mVoiceConnector.isVoiceButtonOnPrimary());
         initSuggest();
         switcher.updateShiftState();
     }
@@ -2240,8 +1877,8 @@
 
     public void swipeRight() {
         if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
-            ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
-            CharSequence text = cm.getText();
+            CharSequence text = ((android.text.ClipboardManager)getSystemService(
+                    CLIPBOARD_SERVICE)).getText();
             if (!TextUtils.isEmpty(text)) {
                 mKeyboardSwitcher.getInputView().startPlaying(text.toString());
             }
@@ -2284,26 +1921,6 @@
         }
     }
 
-    private FieldContext makeFieldContext() {
-        return new FieldContext(
-                getCurrentInputConnection(),
-                getCurrentInputEditorInfo(),
-                mSubtypeSwitcher.getInputLocaleStr(),
-                mSubtypeSwitcher.getEnabledLanguages());
-    }
-
-    private boolean fieldCanDoVoice(FieldContext fieldContext) {
-        return !mPasswordText
-                && mVoiceInput != null
-                && !mVoiceInput.isBlacklistedField(fieldContext);
-    }
-
-    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
-        return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
-                && !(attribute != null
-                        && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
-                && SpeechRecognizer.isRecognitionAvailable(this);
-    }
 
     // receive ringer mode changes to detect silent mode
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -2350,7 +1967,7 @@
         }
     }
 
-    private void vibrate() {
+    public void vibrate() {
         if (!mVibrateOn) {
             return;
         }
@@ -2453,24 +2070,13 @@
                 mResources.getBoolean(R.bool.default_popup_preview));
         mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
         mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
-        mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
-        mHasUsedVoiceInputUnsupportedLocale =
-                sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
-
-        mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported(
-                SubtypeSwitcher.getInstance().getInputLocaleStr());
 
         mAutoCorrectEnabled = isAutoCorrectEnabled(sp);
         mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(sp);
         loadAndSetAutoCompletionThreshold(sp);
 
-        if (VOICE_INSTALLED) {
-            final String voiceMode = sp.getString(PREF_VOICE_MODE,
-                    getString(R.string.voice_mode_main));
-            mVoiceKeyEnabled = !voiceMode.equals(getString(R.string.voice_mode_off))
-                    && shouldShowVoiceButton(makeFieldContext(), attribute);
-            mVoiceButtonOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main));
-        }
+        mVoiceConnector.loadSettings(attribute, sp);
+
         updateCorrectionMode();
         updateAutoTextEnabled();
         updateSuggestionVisibility(sp);
diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
index 960c54d..8da9c22 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
@@ -33,6 +33,7 @@
 import android.text.AutoText;
 import android.util.Log;
 
+import com.android.inputmethod.voice.VoiceIMEConnector;
 import com.android.inputmethod.voice.VoiceInputLogger;
 
 public class LatinIMESettings extends PreferenceActivity
@@ -108,7 +109,7 @@
             ((PreferenceGroup) findPreference(PREDICTION_SETTINGS_KEY))
                     .removePreference(mQuickFixes);
         }
-        if (!LatinIME.VOICE_INSTALLED
+        if (!VoiceIMEConnector.VOICE_INSTALLED
                 || !SpeechRecognizer.isRecognitionAvailable(this)) {
             getPreferenceScreen().removePreference(mVoicePreference);
         } else {
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
index 1242818..fc62053 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -127,8 +127,8 @@
 
     @Override
     protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 
-            XmlResourceParser parser) {
-        Key key = new LatinKey(res, parent, x, y, parser);
+            XmlResourceParser parser, KeyStyles keyStyles) {
+        Key key = new LatinKey(res, parent, x, y, parser, keyStyles);
         switch (key.codes[0]) {
         case LatinIME.KEYCODE_ENTER:
             mEnterKey = key;
@@ -619,8 +619,8 @@
         private boolean mShiftLockEnabled;
 
         public LatinKey(Resources res, BaseKeyboard.Row parent, int x, int y,
-                XmlResourceParser parser) {
-            super(res, parent, x, y, parser);
+                XmlResourceParser parser, KeyStyles keyStyles) {
+            super(res, parent, x, y, parser, keyStyles);
             if (popupCharacters != null && popupCharacters.length() == 0) {
                 // If there is a keyboard with no keys specified in popupCharacters
                 popupResId = 0;
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
new file mode 100644
index 0000000..23b6752
--- /dev/null
+++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.voice;
+
+import com.android.inputmethod.latin.EditingUtil;
+import com.android.inputmethod.latin.Hints;
+import com.android.inputmethod.latin.KeyboardSwitcher;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.LatinIME.UIHandler;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.speech.SpeechRecognizer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class VoiceIMEConnector implements VoiceInput.UiListener {
+    private static final VoiceIMEConnector sInstance = new VoiceIMEConnector();
+
+    public static final boolean VOICE_INSTALLED = true;
+    private static final boolean ENABLE_VOICE_BUTTON = true;
+    private static final String PREF_VOICE_MODE = "voice_mode";
+    // Whether or not the user has used voice input before (and thus, whether to show the
+    // first-run warning dialog or not).
+    private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
+    // Whether or not the user has used voice input from an unsupported locale UI before.
+    // For example, the user has a Chinese UI but activates voice input.
+    private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
+            "has_used_voice_input_unsupported_locale";
+    // The private IME option used to indicate that no microphone should be shown for a
+    // given text field. For instance this is specified by the search dialog when the
+    // dialog is already showing a voice search button.
+    private static final String IME_OPTION_NO_MICROPHONE = "nm";
+
+    private boolean mAfterVoiceInput;
+    private boolean mHasUsedVoiceInput;
+    private boolean mHasUsedVoiceInputUnsupportedLocale;
+    private boolean mImmediatelyAfterVoiceInput;
+    private boolean mIsShowingHint;
+    private boolean mLocaleSupportedForVoiceInput;
+    private boolean mPasswordText;
+    private boolean mRecognizing;
+    private boolean mShowingVoiceSuggestions;
+    private boolean mVoiceButtonEnabled;
+    private boolean mVoiceButtonOnPrimary;
+    private boolean mVoiceInputHighlighted;
+
+    private LatinIME mContext;
+    private AlertDialog mVoiceWarningDialog;
+    private VoiceInput mVoiceInput;
+    private final VoiceResults mVoiceResults = new VoiceResults();
+    private Hints mHints;
+    private UIHandler mHandler;
+    // For each word, a list of potential replacements, usually from voice.
+    private final Map<String, List<CharSequence>> mWordToSuggestions =
+            new HashMap<String, List<CharSequence>>();
+
+    public static VoiceIMEConnector init(LatinIME context, UIHandler h) {
+        sInstance.initInternal(context, h);
+        return sInstance;
+    }
+
+    private void initInternal(LatinIME context, UIHandler h) {
+        mContext = context;
+        mHandler = h;
+        if (VOICE_INSTALLED) {
+            mVoiceInput = new VoiceInput(context, this);
+            mHints = new Hints(context, new Hints.Display() {
+                public void showHint(int viewResource) {
+                    LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                            Context.LAYOUT_INFLATER_SERVICE);
+                    View view = inflater.inflate(viewResource, null);
+                    mContext.setCandidatesView(view);
+                    mContext.setCandidatesViewShown(true);
+                    mIsShowingHint = true;
+                }
+              });
+        }
+    }
+
+    private VoiceIMEConnector() {
+    }
+
+    public void resetVoiceStates(boolean isPasswordText) {
+        mAfterVoiceInput = false;
+        mImmediatelyAfterVoiceInput = false;
+        mShowingVoiceSuggestions = false;
+        mVoiceInputHighlighted = false;
+        mPasswordText = isPasswordText;
+    }
+
+    public void flushVoiceInputLogs(boolean configurationChanged) {
+        if (VOICE_INSTALLED && !configurationChanged) {
+            if (mAfterVoiceInput) {
+                mVoiceInput.flushAllTextModificationCounters();
+                mVoiceInput.logInputEnded();
+            }
+            mVoiceInput.flushLogs();
+            mVoiceInput.cancel();
+        }
+    }
+
+    public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion,
+            String wordSeparators) {
+        if (mAfterVoiceInput && mShowingVoiceSuggestions) {
+            mVoiceInput.flushAllTextModificationCounters();
+            // send this intent AFTER logging any prior aggregated edits.
+            mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
+                    wordSeparators, mContext.getCurrentInputConnection());
+        }
+    }
+
+    private void showVoiceWarningDialog(final boolean swipe, IBinder token,
+            final boolean configurationChanging) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+        builder.setCancelable(true);
+        builder.setIcon(R.drawable.ic_mic_dialog);
+        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                mVoiceInput.logKeyboardWarningDialogOk();
+                reallyStartListening(swipe, configurationChanging);
+            }
+        });
+        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                mVoiceInput.logKeyboardWarningDialogCancel();
+            }
+        });
+
+        if (mLocaleSupportedForVoiceInput) {
+            String message = mContext.getString(R.string.voice_warning_may_not_understand)
+                    + "\n\n" + mContext.getString(R.string.voice_warning_how_to_turn_off);
+            builder.setMessage(message);
+        } else {
+            String message = mContext.getString(R.string.voice_warning_locale_not_supported)
+                    + "\n\n" + mContext.getString(R.string.voice_warning_may_not_understand)
+                    + "\n\n" + mContext.getString(R.string.voice_warning_how_to_turn_off);
+            builder.setMessage(message);
+        }
+
+        builder.setTitle(R.string.voice_warning_title);
+        mVoiceWarningDialog = builder.create();
+        Window window = mVoiceWarningDialog.getWindow();
+        WindowManager.LayoutParams lp = window.getAttributes();
+        lp.token = token;
+        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        window.setAttributes(lp);
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        mVoiceInput.logKeyboardWarningDialogShown();
+        mVoiceWarningDialog.show();
+    }
+
+    public void showPunctuationHintIfNecessary() {
+        InputConnection ic = mContext.getCurrentInputConnection();
+        if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
+            if (mHints.showPunctuationHintIfNecessary(ic)) {
+                mVoiceInput.logPunctuationHintDisplayed();
+            }
+        }
+        mImmediatelyAfterVoiceInput = false;
+    }
+
+    public void hideVoiceWindow(boolean configurationChanging) {
+        if (!configurationChanging) {
+            if (mAfterVoiceInput)
+                mVoiceInput.logInputEnded();
+            if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
+                mVoiceInput.logKeyboardWarningDialogDismissed();
+                mVoiceWarningDialog.dismiss();
+                mVoiceWarningDialog = null;
+            }
+            if (VOICE_INSTALLED & mRecognizing) {
+                mVoiceInput.cancel();
+            }
+        }
+        mWordToSuggestions.clear();
+    }
+
+    public void setCursorAndSelection(int newSelEnd, int newSelStart) {
+        if (mAfterVoiceInput) {
+            mVoiceInput.setCursorPos(newSelEnd);
+            mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
+        }
+    }
+
+    public void setVoiceInputHighlighted(boolean b) {
+        mVoiceInputHighlighted = b;
+    }
+
+    public void setShowingVoiceSuggestions(boolean b) {
+        mShowingVoiceSuggestions = b;
+    }
+
+    public boolean isVoiceButtonEnabled() {
+        return mVoiceButtonEnabled;
+    }
+
+    public boolean isVoiceButtonOnPrimary() {
+        return mVoiceButtonOnPrimary;
+    }
+
+    public boolean isVoiceInputHighlighted() {
+        return mVoiceInputHighlighted;
+    }
+
+    public boolean isRecognizing() {
+        return mRecognizing;
+    }
+
+    public boolean getAndResetIsShowingHint() {
+        boolean ret = mIsShowingHint;
+        mIsShowingHint = false;
+        return ret;
+    }
+
+    private void revertVoiceInput() {
+        InputConnection ic = mContext.getCurrentInputConnection();
+        if (ic != null) ic.commitText("", 1);
+        mContext.updateSuggestions();
+        mVoiceInputHighlighted = false;
+    }
+
+    public void commitVoiceInput() {
+        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+            InputConnection ic = mContext.getCurrentInputConnection();
+            if (ic != null) ic.finishComposingText();
+            mContext.updateSuggestions();
+            mVoiceInputHighlighted = false;
+        }
+    }
+
+    public boolean logAndRevertVoiceInput() {
+        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+            mVoiceInput.incrementTextModificationDeleteCount(
+                    mVoiceResults.candidates.get(0).toString().length());
+            revertVoiceInput();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) {
+        if (mShowingVoiceSuggestions) {
+            // Retain the replaced word in the alternatives array.
+            EditingUtil.Range range = new EditingUtil.Range();
+            String wordToBeReplaced = EditingUtil.getWordAtCursor(
+                    mContext.getCurrentInputConnection(), wordSeparators, range);
+            if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
+                wordToBeReplaced = wordToBeReplaced.toLowerCase();
+            }
+            if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
+                List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
+                if (suggestions.contains(suggestion)) {
+                    suggestions.remove(suggestion);
+                }
+                suggestions.add(wordToBeReplaced);
+                mWordToSuggestions.remove(wordToBeReplaced);
+                mWordToSuggestions.put(suggestion.toString(), suggestions);
+            }
+        }
+    }
+
+    /**
+     * Tries to apply any voice alternatives for the word if this was a spoken word and
+     * there are voice alternatives.
+     * @param touching The word that the cursor is touching, with position information
+     * @return true if an alternative was found, false otherwise.
+     */
+    public boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
+        // Search for result in spoken word alternatives
+        String selectedWord = touching.word.toString().trim();
+        if (!mWordToSuggestions.containsKey(selectedWord)) {
+            selectedWord = selectedWord.toLowerCase();
+        }
+        if (mWordToSuggestions.containsKey(selectedWord)) {
+            mShowingVoiceSuggestions = true;
+            List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+            // If the first letter of touching is capitalized, make all the suggestions
+            // start with a capital letter.
+            if (Character.isUpperCase(touching.word.charAt(0))) {
+                for (int i = 0; i < suggestions.size(); i++) {
+                    String origSugg = (String) suggestions.get(i);
+                    String capsSugg = origSugg.toUpperCase().charAt(0)
+                            + origSugg.subSequence(1, origSugg.length()).toString();
+                    suggestions.set(i, capsSugg);
+                }
+            }
+            mContext.setSuggestions(suggestions, false, true, true);
+            mContext.setCandidatesViewShown(true);
+            return true;
+        }
+        return false;
+    }
+
+    public void handleBackspace() {
+        if (mAfterVoiceInput) {
+            // Don't log delete if the user is pressing delete at
+            // the beginning of the text box (hence not deleting anything)
+            if (mVoiceInput.getCursorPos() > 0) {
+                // If anything was selected before the delete was pressed, increment the
+                // delete count by the length of the selection
+                int deleteLen  =  mVoiceInput.getSelectionSpan() > 0 ?
+                        mVoiceInput.getSelectionSpan() : 1;
+                mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
+            }
+        }
+    }
+
+    public void handleCharacter() {
+        commitVoiceInput();
+        if (mAfterVoiceInput) {
+            // Assume input length is 1. This assumption fails for smiley face insertions.
+            mVoiceInput.incrementTextModificationInsertCount(1);
+        }
+    }
+
+    public void handleSeparator() {
+        commitVoiceInput();
+        if (mAfterVoiceInput){
+            // Assume input length is 1. This assumption fails for smiley face insertions.
+            mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
+        }
+    }
+
+    public void handleClose() {
+        if (VOICE_INSTALLED & mRecognizing) {
+            mVoiceInput.cancel();
+        }
+    }
+
+
+    public void handleVoiceResults(KeyboardSwitcher switcher, boolean capitalizeFirstWord) {
+        mAfterVoiceInput = true;
+        mImmediatelyAfterVoiceInput = true;
+
+        InputConnection ic = mContext.getCurrentInputConnection();
+        if (!mContext.isFullscreenMode()) {
+            // Start listening for updates to the text from typing, etc.
+            if (ic != null) {
+                ExtractedTextRequest req = new ExtractedTextRequest();
+                ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
+            }
+        }
+        mContext.vibrate();
+        mContext.switchToKeyboardView();
+
+        final List<CharSequence> nBest = new ArrayList<CharSequence>();
+        for (String c : mVoiceResults.candidates) {
+            if (capitalizeFirstWord) {
+                c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
+            }
+            nBest.add(c);
+        }
+        if (nBest.size() == 0) {
+            return;
+        }
+        String bestResult = nBest.get(0).toString();
+        mVoiceInput.logVoiceInputDelivered(bestResult.length());
+        mHints.registerVoiceResult(bestResult);
+
+        if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
+        mContext.commitTyped(ic);
+        EditingUtil.appendText(ic, bestResult);
+        if (ic != null) ic.endBatchEdit();
+
+        mVoiceInputHighlighted = true;
+        mWordToSuggestions.putAll(mVoiceResults.alternatives);
+    }
+
+    public void switchToRecognitionStatusView(final boolean configurationChanging) {
+        final boolean configChanged = configurationChanging;
+        mHandler.post(new Runnable() {
+            public void run() {
+                mContext.setCandidatesViewShown(false);
+                mRecognizing = true;
+                View v = mVoiceInput.getView();
+                ViewParent p = v.getParent();
+                if (p != null && p instanceof ViewGroup) {
+                    ((ViewGroup)v.getParent()).removeView(v);
+                }
+                mContext.setInputView(v);
+                mContext.updateInputViewShown();
+                if (configChanged) {
+                    mVoiceInput.onConfigurationChanged();
+                }
+        }});
+    }
+
+    private void reallyStartListening(boolean swipe, final boolean configurationChanging) {
+        if (!mHasUsedVoiceInput) {
+            // The user has started a voice input, so remember that in the
+            // future (so we don't show the warning dialog after the first run).
+            SharedPreferences.Editor editor =
+                    PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
+            SharedPreferencesCompat.apply(editor);
+            mHasUsedVoiceInput = true;
+        }
+
+        if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
+            // The user has started a voice input from an unsupported locale, so remember that
+            // in the future (so we don't show the warning dialog the next time they do this).
+            SharedPreferences.Editor editor =
+                    PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
+            SharedPreferencesCompat.apply(editor);
+            mHasUsedVoiceInputUnsupportedLocale = true;
+        }
+
+        // Clear N-best suggestions
+        mContext.clearSuggestions();
+
+        FieldContext context = makeFieldContext();
+        mVoiceInput.startListening(context, swipe);
+        switchToRecognitionStatusView(configurationChanging);
+    }
+
+    public void startListening(final boolean swipe, IBinder token,
+            final boolean configurationChanging) {
+        if (VOICE_INSTALLED) {
+            if (!mHasUsedVoiceInput ||
+                    (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
+                // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
+                showVoiceWarningDialog(swipe, token, configurationChanging);
+            } else {
+                reallyStartListening(swipe, configurationChanging);
+            }
+        }
+    }
+
+
+    private boolean fieldCanDoVoice(FieldContext fieldContext) {
+        return !mPasswordText
+                && mVoiceInput != null
+                && !mVoiceInput.isBlacklistedField(fieldContext);
+    }
+
+    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+        return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
+                && !(attribute != null
+                        && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
+                && SpeechRecognizer.isRecognitionAvailable(mContext);
+    }
+
+    public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
+        mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
+        mHasUsedVoiceInputUnsupportedLocale =
+                sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
+
+        mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported(
+                SubtypeSwitcher.getInstance().getInputLocaleStr());
+
+        if (VOICE_INSTALLED) {
+            final String voiceMode = sp.getString(PREF_VOICE_MODE,
+                    mContext.getString(R.string.voice_mode_main));
+            mVoiceButtonEnabled = !voiceMode.equals(mContext.getString(R.string.voice_mode_off))
+                    && shouldShowVoiceButton(makeFieldContext(), attribute);
+            mVoiceButtonOnPrimary = voiceMode.equals(mContext.getString(R.string.voice_mode_main));
+        }
+    }
+
+    public void destroy() {
+        if (VOICE_INSTALLED && mVoiceInput != null) {
+            mVoiceInput.destroy();
+        }
+    }
+
+    public void onConfigurationChanged(boolean configurationChanging) {
+        if (mRecognizing) {
+            switchToRecognitionStatusView(configurationChanging);
+        }
+    }
+    @Override
+    public void onCancelVoice() {
+        if (mRecognizing) {
+            mRecognizing = false;
+            mContext.switchToKeyboardView();
+        }
+    }
+
+    @Override
+    public void onVoiceResults(List<String> candidates,
+            Map<String, List<CharSequence>> alternatives) {
+        if (!mRecognizing) {
+            return;
+        }
+        mVoiceResults.candidates = candidates;
+        mVoiceResults.alternatives = alternatives;
+        mHandler.updateVoiceResults();
+    }
+
+    public FieldContext makeFieldContext() {
+        SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
+        return new FieldContext(mContext.getCurrentInputConnection(),
+                mContext.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
+                switcher.getEnabledLanguages());
+    }
+
+    private class VoiceResults {
+        List<String> candidates;
+        Map<String, List<CharSequence>> alternatives;
+    }
+}