am 3678ce5a: (-s ours) am 991e88c5: am 4bad47f9: (-s ours) Import revised translations.  DO NOT MERGE

* commit '3678ce5a2513de3b7f617238d6d5549f2697d017':
  Import revised translations.  DO NOT MERGE
diff --git a/java/proguard.flags b/java/proguard.flags
index 729f4ad..914bd75 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -18,3 +18,7 @@
 -keep class com.android.inputmethod.latin.AutoCorrection {
   java.lang.CharSequence getAutoCorrectionWord();
 }
+
+-keep class com.android.inputmethod.latin.Utils {
+  boolean equalsIgnoreCase(...);
+}
diff --git a/java/res/layout/recognition_status.xml b/java/res/layout/recognition_status.xml
index 9474d6f..45f6897 100644
--- a/java/res/layout/recognition_status.xml
+++ b/java/res/layout/recognition_status.xml
@@ -45,7 +45,7 @@
                 android:layout_height="0dip"
                 android:layout_width="match_parent"
                 android:layout_weight="1.0">
-            <com.android.inputmethod.voice.SoundIndicator
+            <com.android.inputmethod.deprecated.voice.SoundIndicator
                     android:id="@+id/sound_indicator"
                     android:src="@drawable/mic_full"
                     android:background="@drawable/mic_base"
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 633b159..7edd988 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -41,7 +41,7 @@
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Zawsze pokazuj"</string>
     <string name="settings_key_mode_always_hide_name" msgid="7833948046716923994">"Zawsze ukrywaj"</string>
     <string name="auto_correction" msgid="4979925752001319458">"Autokorekta"</string>
-    <string name="auto_correction_summary" msgid="5625751551134658006">"Spacja i znaki przestankowe poprawiają błędnie wpisane słowa"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Spacja i znaki przestankowe powodują automatyczne poprawianie błędnie napisanych słów"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Wyłącz"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Umiarkowana"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresywna"</string>
@@ -71,7 +71,7 @@
     <string name="voice_warning_title" msgid="4419354150908395008">"Wprowadzanie głosowe"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Wprowadzanie głosowe obecnie nie jest obsługiwane w Twoim języku, ale działa w języku angielskim."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Funkcja wprowadzania głosowego wykorzystuje mechanizm rozpoznawania mowy. Obowiązuje "<a href="http://m.google.com/privacy">"Polityka prywatności Google Mobile"</a>"."</string>
-    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Aby wyłączyć rozpoznawanie mowy, przejdź do ustawień sposobu wprowadzania tekstu."</string>
+    <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Aby wyłączyć wprowadzanie głosowe, przejdź do ustawień metody wprowadzania."</string>
     <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Aby użyć wprowadzania głosowego, naciśnij przycisk mikrofonu."</string>
     <string name="voice_listening" msgid="467518160751321844">"Mów teraz"</string>
     <string name="voice_working" msgid="6666937792815731889">"W toku"</string>
@@ -95,13 +95,13 @@
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon na klawiaturze głównej"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrofon na klawiaturze z symbolami"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Wprowadzanie głosowe jest wyłączone"</string>
-    <string name="selectInputMethod" msgid="315076553378705821">"Wybierz sposób wprowadzania tekstu"</string>
+    <string name="selectInputMethod" msgid="315076553378705821">"Wybierz metodę wprowadzania"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Języki wprowadzania"</string>
     <string name="language_selection_summary" msgid="187110938289512256">"Przesuń palcem po spacji, aby zmienić język"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Dotknij ponownie, aby zapisać"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Słownik dostępny"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Włącz przesyłanie opinii użytkownika"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Pomóż ulepszyć edytor wprowadzania tekstu, automatycznie wysyłając do Google statystyki użycia i raporty o awariach."</string>
+    <string name="prefs_description_log" msgid="5827825607258246003">"Pomóż ulepszyć edytor tej metody wprowadzania, automatycznie wysyłając do Google statystyki użycia i raporty o awariach."</string>
     <string name="prefs_enable_recorrection" msgid="4588408906649533582">"Popraw dotknięte słowo"</string>
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dotykaj wprowadzonych słów, aby je poprawiać tylko wówczas, gdy widoczne są sugestie."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Motyw klawiatury"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index f8fdab0..21fd02b 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -28,7 +28,7 @@
     <string name="popup_on_keypress" msgid="123894815723512944">"Увеличение нажатых"</string>
     <string name="general_category" msgid="1859088467017573195">"Общие"</string>
     <string name="prediction_category" msgid="6361242011806282176">"Коррекция текста"</string>
-    <string name="auto_cap" msgid="1719746674854628252">"Заглавные автоматически"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Автоподст. заглавных"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Быстрое исправление"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Исправлять распространенные опечатки"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Показать варианты исправлений"</string>
@@ -94,7 +94,7 @@
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Выкл."</string>
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Значок на основной клавиатуре"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Значок на клавиатуре символов"</string>
-    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голосовой ввод откл."</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голосовой ввод отключен"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Выбрать способ ввода"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Языки ввода"</string>
     <string name="language_selection_summary" msgid="187110938289512256">"Для изменения языка проведите пальцем по пробелу"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 71a6215..1baed4d 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -94,7 +94,7 @@
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Kapalı"</string>
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Ana klavyede mikrfn"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Simge klavysnd mikrf"</string>
-    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Sesle grş devre dışı"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Sesle giriş devr dşı"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Giriş yöntemini seç"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Giriş dilleri"</string>
     <string name="language_selection_summary" msgid="187110938289512256">"Dili değiştirmek için parmağınızı boşluk çubuğu üzerinde kaydırın"</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 3c0a9c1..66dc7d7 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -242,6 +242,8 @@
 
     <!-- Description for Czech keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_cs_keyboard">Czech Keyboard</string>
+    <!-- Description for Arabic keyboard subtype [CHAR LIMIT=35] -->
+    <string name="subtype_mode_ar_keyboard">Arabic Keyboard</string>
     <!-- Description for Danish keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_da_keyboard">Danish Keyboard</string>
     <!-- Description for German keyboard subtype [CHAR LIMIT=35] -->
@@ -260,6 +262,9 @@
     <string name="subtype_mode_fr_CA_keyboard">French (Canada) Keyboard</string>
     <!-- Description for French (Switzerland) keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_fr_CH_keyboard">French (Switzerland) Keyboard</string>
+    <!-- Description for Hebrew keyboard subtype [CHAR LIMIT=35] -->
+    <!-- Java uses the deprecated "iw" code instead of the standard "he" code -->
+    <string name="subtype_mode_iw_keyboard">Hebrew Keyboard</string>
     <!-- Description for Italian keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_it_keyboard">Italian Keyboard</string>
     <!-- Description for Norwegian keyboard subtype [CHAR LIMIT=35] -->
diff --git a/java/res/xml-ar/kbd_qwerty.xml b/java/res/xml-ar/kbd_qwerty.xml
new file mode 100644
index 0000000..4c6a4f5
--- /dev/null
+++ b/java/res/xml-ar/kbd_qwerty.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardHeight="@dimen/keyboardHeight"
+    latin:maxKeyboardHeight="50%p"
+    latin:rowHeight="25%p"
+    latin:keyWidth="10%p"
+    latin:horizontalGap="@dimen/key_horizontal_gap"
+    latin:verticalGap="@dimen/key_bottom_gap"
+    latin:popupKeyboardTemplate="@xml/kbd_popup_template"
+    latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_ar_rows" />
+</Keyboard>
diff --git a/java/res/xml-iw/kbd_qwerty.xml b/java/res/xml-iw/kbd_qwerty.xml
index 98bfd7e..72826d4 100644
--- a/java/res/xml-iw/kbd_qwerty.xml
+++ b/java/res/xml-iw/kbd_qwerty.xml
@@ -1,19 +1,19 @@
 <?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 
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
 **
-**     http://www.apache.org/licenses/LICENSE-2.0 
+**     http://www.apache.org/licenses/LICENSE-2.0
 **
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
@@ -30,83 +30,5 @@
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:rowEdgeFlags="top"
-    >
-        <Spacer
-            latin:horizontalGap="5%p" />
-        <Key
-            latin:keyLabel="ק"
-            latin:keyEdgeFlags="left" />
-        <Key
-            latin:keyLabel="ר" />
-        <Key
-            latin:keyLabel="א" />
-        <Key
-            latin:keyLabel="ט" />
-        <Key
-            latin:keyLabel="ו" />
-        <Key
-            latin:keyLabel="ן" />
-        <Key
-            latin:keyLabel="ם" />
-        <Key
-            latin:keyLabel="פ" />
-        <Spacer
-            latin:horizontalGap="1.25%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="13.75%p"
-            latin:keyEdgeFlags="right" />
-    </Row>
-    <Row>
-        <Key
-            latin:keyLabel="ש"
-            latin:keyEdgeFlags="left" />
-        <Key
-            latin:keyLabel="ד" />
-        <Key
-            latin:keyLabel="ג" />
-        <Key
-            latin:keyLabel="כ" />
-        <Key
-            latin:keyLabel="ע" />
-        <Key
-            latin:keyLabel="י" />
-        <Key
-            latin:keyLabel="ח" />
-        <Key
-            latin:keyLabel="ל" />
-        <Key
-            latin:keyLabel="ך" />
-        <Key
-            latin:keyLabel="ף"
-            latin:keyEdgeFlags="right" />
-    </Row>
-    <Row>
-        <Spacer
-            latin:horizontalGap="5%p" />
-        <Key
-            latin:keyLabel="ז"
-            latin:keyEdgeFlags="left" />
-        <Key
-            latin:keyLabel="ס" />
-        <Key
-            latin:keyLabel="ב" />
-        <Key
-            latin:keyLabel="ה" />
-        <Key
-            latin:keyLabel="נ" />
-        <Key
-            latin:keyLabel="מ" />
-        <Key
-            latin:keyLabel="צ" />
-        <Key
-            latin:keyLabel="ת" />
-        <Key
-            latin:keyLabel="ץ"
-            latin:keyEdgeFlags="right" />
-    </Row>
-    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+        latin:keyboardLayout="@xml/kbd_iw_rows" />
 </Keyboard>
diff --git a/java/res/xml-xlarge/kbd_ar_rows.xml b/java/res/xml-xlarge/kbd_ar_rows.xml
new file mode 100644
index 0000000..e84aae6
--- /dev/null
+++ b/java/res/xml-xlarge/kbd_ar_rows.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file for Arabic layout is an alpha version. It allows to enter   -->
+<!-- some right-to-left text, but it has gone through no study whatsoever, -->
+<!-- and needs to be run through UX.                                       -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:keyWidth="7.49%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="7.949%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ض" />
+        <Key
+            latin:keyLabel="ص" />
+        <Key
+            latin:keyLabel="ث" />
+        <Key
+            latin:keyLabel="ق" />
+        <Key
+            latin:keyLabel="ف"
+            latin:popupCharacters="ف,ڤ" />
+        <Key
+            latin:keyLabel="غ" />
+        <Key
+            latin:keyLabel="ع" />
+        <Key
+            latin:keyLabel="ه"
+            latin:popupCharacters="ه,هـ" />
+        <Key
+            latin:keyLabel="خ" />
+        <Key
+            latin:keyLabel="ح" />
+        <Key
+            latin:keyLabel="ج"
+            latin:popupCharacters="ج,چ" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="9.331%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="7.49%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="7.949%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ش" />
+        <Key
+            latin:keyLabel="س" />
+        <Key
+            latin:keyLabel="ي" />
+        <Key
+            latin:keyLabel="ب"
+            latin:popupCharacters="ب,پ" />
+        <Key
+            latin:keyLabel="ل"
+            latin:popupCharacters="ل,لا" />
+        <Key
+            latin:keyLabel="ا"
+            latin:popupCharacters="ا,أ,إ,آ" />
+        <Key
+            latin:keyLabel="ت" />
+        <Key
+            latin:keyLabel="ن" />
+        <Key
+            latin:keyLabel="م" />
+        <Key
+            latin:keyLabel="ك"
+            latin:popupCharacters="ك,گ" />
+        <Key
+            latin:keyLabel="ط" />
+        <Key
+            latin:keyStyle="returnKeyStyle"
+            latin:keyWidth="8.593%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="8.042%p"
+    >
+        <Key
+            latin:keyLabel="ئ"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ء" />
+        <Key
+            latin:keyLabel="ؤ" />
+        <Key
+            latin:keyLabel="ر" />
+        <Key
+            latin:keyLabel="ذ" />
+        <Key
+            latin:keyLabel="ى" />
+        <Key
+            latin:keyLabel="ة" />
+        <Key
+            latin:keyLabel="و" />
+        <Key
+            latin:keyLabel="ز"
+            latin:popupCharacters="ز,ژ" />
+        <Key
+            latin:keyLabel="ظ" />
+        <Key
+            latin:keyLabel="د" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="11.736%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml-xlarge/kbd_iw_rows.xml b/java/res/xml-xlarge/kbd_iw_rows.xml
new file mode 100644
index 0000000..a3a239d
--- /dev/null
+++ b/java/res/xml-xlarge/kbd_iw_rows.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:keyWidth="8.272%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="7.949%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="," />
+        <Key
+            latin:keyLabel="." />
+        <Key
+            latin:keyLabel="ק" />
+        <Key
+            latin:keyLabel="ר" />
+        <Key
+            latin:keyLabel="א" />
+        <Key
+            latin:keyLabel="ט" />
+        <Key
+            latin:keyLabel="ו" />
+        <Key
+            latin:keyLabel="ן" />
+        <Key
+            latin:keyLabel="ם" />
+        <Key
+            latin:keyLabel="פ" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="9.331%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="8.157%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="10.167%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ש" />
+        <Key
+            latin:keyLabel="ד" />
+        <Key
+            latin:keyLabel="ג"
+            latin:popupCharacters="ג,ג׳" />
+        <Key
+            latin:keyLabel="כ" />
+        <Key
+            latin:keyLabel="ע" />
+        <Key
+            latin:keyLabel="י"
+            latin:popupCharacters="י,ײַ" />
+        <Key
+            latin:keyLabel="ח"
+            latin:popupCharacters="ח,ח׳" />
+        <Key
+            latin:keyLabel="ל" />
+        <Key
+            latin:keyLabel="ך" />
+        <Key
+            latin:keyLabel="ף" />
+        <Key
+            latin:keyStyle="returnKeyStyle"
+            latin:keyWidth="8.593%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="8.042%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="15.192%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ז"
+            latin:popupCharacters="ז,ז׳" />
+        <Key
+            latin:keyLabel="ס" />
+        <Key
+            latin:keyLabel="ב" />
+        <Key
+            latin:keyLabel="ה" />
+        <Key
+            latin:keyLabel="נ" />
+        <Key
+            latin:keyLabel="מ" />
+        <Key
+            latin:keyLabel="צ"
+            latin:popupCharacters="צ,צ׳" />
+        <Key
+            latin:keyLabel="ת"
+            latin:popupCharacters="ת,ת׳" />
+        <Key
+            latin:keyLabel="ץ"
+            latin:popupCharacters="ץ,ץ׳" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="12.530%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml/kbd_iw_rows.xml b/java/res/xml/kbd_iw_rows.xml
new file mode 100644
index 0000000..db49ad7
--- /dev/null
+++ b/java/res/xml/kbd_iw_rows.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file for Hebrew layout is an alpha version. It allows to enter   -->
+<!-- some right-to-left text, but it has gone through no study whatsoever, -->
+<!-- and needs to be run through UX.                                       -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:rowEdgeFlags="top"
+    >
+        <Spacer
+            latin:horizontalGap="5%p" />
+        <Key
+            latin:keyLabel="ק"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ר" />
+        <Key
+            latin:keyLabel="א" />
+        <Key
+            latin:keyLabel="ט" />
+        <Key
+            latin:keyLabel="ו" />
+        <Key
+            latin:keyLabel="ן" />
+        <Key
+            latin:keyLabel="ם" />
+        <Key
+            latin:keyLabel="פ" />
+        <Spacer
+            latin:horizontalGap="1.25%p" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="13.75%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyLabel="ש"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ד" />
+        <Key
+            latin:keyLabel="ג" />
+        <Key
+            latin:keyLabel="כ" />
+        <Key
+            latin:keyLabel="ע" />
+        <Key
+            latin:keyLabel="י" />
+        <Key
+            latin:keyLabel="ח" />
+        <Key
+            latin:keyLabel="ל" />
+        <Key
+            latin:keyLabel="ך" />
+        <Key
+            latin:keyLabel="ף"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Spacer
+            latin:horizontalGap="5%p" />
+        <Key
+            latin:keyLabel="ז"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ס" />
+        <Key
+            latin:keyLabel="ב" />
+        <Key
+            latin:keyLabel="ה" />
+        <Key
+            latin:keyLabel="נ" />
+        <Key
+            latin:keyLabel="מ" />
+        <Key
+            latin:keyLabel="צ" />
+        <Key
+            latin:keyLabel="ת" />
+        <Key
+            latin:keyLabel="ץ"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 8dec7ab..1374338 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -45,6 +45,12 @@
             android:imeSubtypeLocale="en_GB"
             android:imeSubtypeMode="keyboard"
     />
+    <!-- The file for Arabic layout is an alpha version. It needs to be run through UX. -->
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_mode_ar_keyboard"
+            android:imeSubtypeLocale="ar"
+            android:imeSubtypeMode="keyboard"
+    />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_cs_keyboard"
             android:imeSubtypeLocale="cs"
@@ -110,6 +116,13 @@
             android:imeSubtypeLocale="it"
             android:imeSubtypeMode="keyboard"
     />
+    <!-- Java uses the deprecated "iw" code instead of the standard "he" code for Hebrew. -->
+    <!-- The file for Hebrew layout is an alpha version. It needs to be run through UX. -->
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_mode_iw_keyboard"
+            android:imeSubtypeLocale="iw"
+            android:imeSubtypeMode="keyboard"
+    />
     <subtype android:icon="@drawable/ic_subtype_mic"
             android:label="@string/subtype_mode_it_voice"
             android:imeSubtypeLocale="it"
diff --git a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
new file mode 100644
index 0000000..6594935
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.util.Log;
+
+public abstract class AbstractCompatWrapper {
+    private static final String TAG = AbstractCompatWrapper.class.getSimpleName();
+    protected final Object mObj;
+
+    public AbstractCompatWrapper(Object obj) {
+        if (obj == null) {
+            Log.e(TAG, "Invalid input to AbstructCompatWrapper");
+        }
+        mObj = obj;
+    }
+
+    public Object getOriginalObject() {
+        return mObj;
+    }
+
+    public boolean hasOriginalObject() {
+        return mObj != null;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
new file mode 100644
index 0000000..f06760e
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompatUtils {
+    private static final String TAG = CompatUtils.class.getSimpleName();
+    private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    // TODO: Can these be constants instead of literal String constants?
+    private static final String INPUT_METHOD_SUBTYPE_SETTINGS =
+            "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
+    private static final String INPUT_LANGUAGE_SELECTION =
+            "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
+
+    public static Intent getInputLanguageSelectionIntent(String inputMethodId,
+            int flagsForSubtypeSettings) {
+        final String action;
+        Intent intent;
+        if (android.os.Build.VERSION.SDK_INT
+                >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
+            // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+            action = INPUT_METHOD_SUBTYPE_SETTINGS;
+            intent = new Intent(action);
+            if (!TextUtils.isEmpty(inputMethodId)) {
+                intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
+            }
+            if (flagsForSubtypeSettings > 0) {
+                intent.setFlags(flagsForSubtypeSettings);
+            }
+        } else {
+            action = INPUT_LANGUAGE_SELECTION;
+            intent = new Intent(action);
+        }
+        return intent;
+    }
+
+    public static Class<?> getClass(String className) {
+        try {
+            return Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class<?> targetClass, String name,
+            Class<?>... parameterTypes) {
+        if (targetClass == null || TextUtils.isEmpty(name)) return null;
+        try {
+            return targetClass.getMethod(name, parameterTypes);
+        } catch (SecurityException e) {
+            // ignore
+            return null;
+        } catch (NoSuchMethodException e) {
+            // ignore
+            return null;
+        }
+    }
+
+    public static Field getField(Class<?> targetClass, String name) {
+        try {
+            return targetClass.getField(name);
+        } catch (SecurityException e) {
+            // ignore
+            return null;
+        } catch (NoSuchFieldException e) {
+            // ignore
+            return null;
+        }
+    }
+
+    public static Constructor<?> getConstructor(Class<?> targetClass, Class<?>[] types) {
+        if (targetClass == null || types == null) return null;
+        try {
+            return targetClass.getConstructor(types);
+        } catch (SecurityException e) {
+            // ignore
+            return null;
+        } catch (NoSuchMethodException e) {
+            // ignore
+            return null;
+        }
+    }
+
+    public static Object invoke(
+            Object receiver, Object defaultValue, Method method, Object... args) {
+        if (receiver == null || method == null) return defaultValue;
+        try {
+            return method.invoke(receiver, args);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception in invoke: IllegalArgumentException");
+            return defaultValue;
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Exception in invoke: IllegalAccessException");
+            return defaultValue;
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Exception in invoke: IllegalTargetException");
+            return defaultValue;
+        }
+    }
+
+    public static Object getFieldValue(Object receiver, Object defaultValue, Field field) {
+        if (receiver == null || field == null) return defaultValue;
+        try {
+            return field.get(receiver);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception in getFieldValue: IllegalArgumentException");
+            return defaultValue;
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Exception in getFieldValue: IllegalAccessException");
+            return defaultValue;
+        }
+    }
+
+    public static void setFieldValue(Object receiver, Field field, Object value) {
+        if (receiver == null || field == null) return;
+        try {
+            field.set(receiver, value);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception in setFieldValue: IllegalArgumentException");
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Exception in setFieldValue: IllegalAccessException");
+        }
+    }
+
+    public static List<InputMethodSubtypeCompatWrapper> copyInputMethodSubtypeListToWrapper(
+            Object listObject) {
+        if (!(listObject instanceof List<?>)) return null;
+        final List<InputMethodSubtypeCompatWrapper> subtypes =
+                new ArrayList<InputMethodSubtypeCompatWrapper>();
+        for (Object o: (List<?>)listObject) {
+            subtypes.add(new InputMethodSubtypeCompatWrapper(o));
+        }
+        return subtypes;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
new file mode 100644
index 0000000..f6f4f7a
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import java.lang.reflect.Field;
+
+public class EditorInfoCompatUtils {
+    private static final Field FIELD_IME_FLAG_NAVIGATE_NEXT = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT");
+    private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS");
+    private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_ACTION_PREVIOUS");
+    private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT);
+    private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS);
+    private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS);
+
+    public static boolean hasFlagNavigateNext(int imeOptions) {
+        if (OBJ_IME_FLAG_NAVIGATE_NEXT == null)
+            return false;
+        return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0;
+    }
+
+    public static boolean hasFlagNavigatePrevious(int imeOptions) {
+        if (OBJ_IME_FLAG_NAVIGATE_PREVIOUS == null)
+            return false;
+        return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0;
+    }
+
+    public static void performEditorActionNext(InputConnection ic) {
+        ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
+    }
+
+    public static void performEditorActionPrevious(InputConnection ic) {
+        if (OBJ_IME_ACTION_PREVIOUS == null)
+            return;
+        ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS);
+    }
+
+    public static String imeOptionsName(int imeOptions) {
+        if (imeOptions == -1)
+            return null;
+        final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION;
+        final String action;
+        switch (actionId) {
+            case EditorInfo.IME_ACTION_UNSPECIFIED:
+                action = "actionUnspecified";
+                break;
+            case EditorInfo.IME_ACTION_NONE:
+                action = "actionNone";
+                break;
+            case EditorInfo.IME_ACTION_GO:
+                action = "actionGo";
+                break;
+            case EditorInfo.IME_ACTION_SEARCH:
+                action = "actionSearch";
+                break;
+            case EditorInfo.IME_ACTION_SEND:
+                action = "actionSend";
+                break;
+            case EditorInfo.IME_ACTION_DONE:
+                action = "actionDone";
+                break;
+            default: {
+                if (OBJ_IME_ACTION_PREVIOUS != null && actionId == OBJ_IME_ACTION_PREVIOUS) {
+                    action = "actionPrevious";
+                } else {
+                    action = "actionUnknown(" + actionId + ")";
+                }
+                break;
+            }
+        }
+        if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+            return "flagNoEnterAction|" + action;
+        } else {
+            return action;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java
new file mode 100644
index 0000000..b4fe327
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.util.Log;
+import android.view.inputmethod.InputConnection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class InputConnectionCompatUtils {
+    private static final String TAG = InputConnectionCompatUtils.class.getSimpleName();
+    private static final Class<?> CLASS_CorrectionInfo = CompatUtils
+            .getClass("android.view.inputmethod.CorrectionInfo");
+    private static final Class<?>[] INPUT_TYPE_CorrectionInfo = new Class<?>[] { int.class,
+            CharSequence.class, CharSequence.class };
+    private static final Constructor<?> CONSTRUCTOR_CorrectionInfo = CompatUtils
+            .getConstructor(CLASS_CorrectionInfo, INPUT_TYPE_CorrectionInfo);
+    private static final Method METHOD_InputConnection_commitCorrection = CompatUtils
+            .getMethod(InputConnection.class, "commitCorrection", CLASS_CorrectionInfo);
+
+    public static void commitCorrection(InputConnection ic, int offset, CharSequence oldText,
+            CharSequence newText) {
+        if (ic == null || CONSTRUCTOR_CorrectionInfo == null
+                || METHOD_InputConnection_commitCorrection == null) {
+            return;
+        }
+        Object[] args = { offset, oldText, newText };
+        try {
+            Object correctionInfo = CONSTRUCTOR_CorrectionInfo.newInstance(args);
+            CompatUtils.invoke(ic, null, METHOD_InputConnection_commitCorrection,
+                    correctionInfo);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Error in commitCorrection: IllegalArgumentException");
+        } catch (InstantiationException e) {
+            Log.e(TAG, "Error in commitCorrection: InstantiationException");
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Error in commitCorrection: IllegalAccessException");
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Error in commitCorrection: InvocationTargetException");
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java
new file mode 100644
index 0000000..8e22bbc
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+
+import java.lang.reflect.Method;
+
+public class InputMethodInfoCompatWrapper {
+    private final InputMethodInfo mImi;
+    private static final Method METHOD_getSubtypeAt = CompatUtils.getMethod(
+            InputMethodInfo.class, "getSubtypeAt", int.class);
+    private static final Method METHOD_getSubtypeCount = CompatUtils.getMethod(
+            InputMethodInfo.class, "getSubtypeCount");
+
+    public InputMethodInfoCompatWrapper(InputMethodInfo imi) {
+        mImi = imi;
+    }
+
+    public InputMethodInfo getInputMethodInfo() {
+        return mImi;
+    }
+
+    public String getId() {
+        return mImi.getId();
+    }
+
+    public String getPackageName() {
+        return mImi.getPackageName();
+    }
+
+    public ServiceInfo getServiceInfo() {
+        return mImi.getServiceInfo();
+    }
+
+    public int getSubtypeCount() {
+        return (Integer) CompatUtils.invoke(mImi, 0, METHOD_getSubtypeCount);
+    }
+
+    public InputMethodSubtypeCompatWrapper getSubtypeAt(int index) {
+        return new InputMethodSubtypeCompatWrapper(CompatUtils.invoke(mImi, null,
+                METHOD_getSubtypeAt, index));
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
new file mode 100644
index 0000000..5e66bf4
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// TODO: Override this class with the concrete implementation if we need to take care of the
+// performance.
+public class InputMethodManagerCompatWrapper {
+    private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
+    private static final Method METHOD_getCurrentInputMethodSubtype =
+            CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype");
+    private static final Method METHOD_getEnabledInputMethodSubtypeList =
+            CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList",
+                    InputMethodInfo.class, boolean.class);
+    private static final Method METHOD_getShortcutInputMethodsAndSubtypes =
+            CompatUtils.getMethod(InputMethodManager.class, "getShortcutInputMethodsAndSubtypes");
+    private static final Method METHOD_setInputMethodAndSubtype =
+            CompatUtils.getMethod(
+                    InputMethodManager.class, "setInputMethodAndSubtype", IBinder.class,
+                    String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype);
+    private static final Method METHOD_switchToLastInputMethod = CompatUtils.getMethod(
+            InputMethodManager.class, "switchToLastInputMethod", IBinder.class);
+
+    private static final InputMethodManagerCompatWrapper sInstance =
+            new InputMethodManagerCompatWrapper();
+
+    private InputMethodManager mImm;
+    private InputMethodManagerCompatWrapper() {
+    }
+
+    public static InputMethodManagerCompatWrapper getInstance(Context context) {
+        if (sInstance.mImm == null) {
+            sInstance.init(context);
+        }
+        return sInstance;
+    }
+
+    private synchronized void init(Context context) {
+        mImm = (InputMethodManager) context.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+    }
+
+    public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() {
+        return new InputMethodSubtypeCompatWrapper(
+                CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype));
+    }
+
+    public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
+            InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
+        Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList,
+                (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes);
+        // Returns an empty list
+        if (retval == null)
+            return Collections.emptyList();
+        return CompatUtils.copyInputMethodSubtypeListToWrapper((List<?>)retval);
+    }
+
+    public Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
+            getShortcutInputMethodsAndSubtypes() {
+        Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes);
+        // Returns an empty map
+        if (!(retval instanceof Map)) return Collections.emptyMap();
+        Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap =
+                new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>();
+        final Map<?, ?> retvalMap = (Map<?, ?>)retval;
+        for (Object key : retvalMap.keySet()) {
+            if (!(key instanceof InputMethodInfo)) {
+                Log.e(TAG, "Class type error.");
+                return null;
+            }
+            shortcutMap.put(new InputMethodInfoCompatWrapper((InputMethodInfo)key),
+                    CompatUtils.copyInputMethodSubtypeListToWrapper(retvalMap.get(key)));
+        }
+        return shortcutMap;
+    }
+
+    public void setInputMethodAndSubtype(
+            IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) {
+        CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype,
+                token, id, subtype.getOriginalObject());
+    }
+
+    public boolean switchToLastInputMethod(IBinder token) {
+        return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
+    }
+
+    public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() {
+        if (mImm == null) return null;
+        List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>();
+        for (InputMethodInfo imi : mImm.getEnabledInputMethodList()) {
+            imis.add(new InputMethodInfoCompatWrapper(imi));
+        }
+        return imis;
+    }
+
+    public void showInputMethodPicker() {
+        if (mImm == null) return;
+        mImm.showInputMethodPicker();
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
new file mode 100644
index 0000000..e02aac7
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
+import android.inputmethodservice.InputMethodService;
+import android.view.View;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.HorizontalScrollView;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class InputMethodServiceCompatWrapper extends InputMethodService {
+    private static final Method METHOD_HorizontalScrollView_setOverScrollMode =
+            CompatUtils.getMethod(HorizontalScrollView.class, "setOverScrollMode", int.class);
+    private static final Field FIELD_View_OVER_SCROLL_NEVER =
+            CompatUtils.getField(View.class, "OVER_SCROLL_NEVER");
+    private static final Integer View_OVER_SCROLL_NEVER =
+            (Integer)CompatUtils.getFieldValue(null, null, FIELD_View_OVER_SCROLL_NEVER);
+    // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10
+    // or previous. Note that InputMethodSubtype was added in the API level 11.
+    // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged().
+    // For the API level 10 or previous, we handle the "subtype changed" events by ourselves
+    // without having support from framework -- onCurrentInputMethodSubtypeChanged().
+    private static final boolean CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED = true;
+
+    private InputMethodManagerCompatWrapper mImm;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mImm = InputMethodManagerCompatWrapper.getInstance(this);
+    }
+
+    // When the API level is 10 or previous, notifyOnCurrentInputMethodSubtypeChanged should
+    // handle the event the current subtype was changed. LatinIME calls
+    // notifyOnCurrentInputMethodSubtypeChanged every time LatinIME
+    // changes the current subtype.
+    // This call is required to let LatinIME itself know a subtype changed
+    // event when the API level is 10 or previous.
+    @SuppressWarnings("unused")
+    public void notifyOnCurrentInputMethodSubtypeChanged(InputMethodSubtypeCompatWrapper subtype) {
+        // Do nothing when the API level is 11 or later
+        if (CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return;
+        if (subtype == null) {
+            subtype = mImm.getCurrentInputMethodSubtype();
+        }
+        if (subtype != null) {
+            SubtypeSwitcher.getInstance().updateSubtype(subtype);
+        }
+    }
+
+    protected static void setOverScrollModeNever(HorizontalScrollView scrollView) {
+        if (View_OVER_SCROLL_NEVER != null) {
+            CompatUtils.invoke(scrollView, null, METHOD_HorizontalScrollView_setOverScrollMode,
+                    View_OVER_SCROLL_NEVER);
+        }
+    }
+
+    //////////////////////////////////////
+    // Functions using API v11 or later //
+    //////////////////////////////////////
+    @Override
+    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
+        // Do nothing when the API level is 10 or previous
+        if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return;
+        SubtypeSwitcher.getInstance().updateSubtype(
+                new InputMethodSubtypeCompatWrapper(subtype));
+    }
+
+    protected static void setTouchableRegionCompat(InputMethodService.Insets outInsets,
+            int x, int y, int width, int height) {
+        outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
+        outInsets.touchableRegion.set(x, y, width, height);
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
new file mode 100644
index 0000000..3ffa819
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+
+// TODO: Override this class with the concrete implementation if we need to take care of the
+// performance.
+public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper {
+    private static final boolean DBG = LatinImeLogger.sDBG;
+    private static final String TAG = InputMethodSubtypeCompatWrapper.class.getSimpleName();
+    private static final String DEFAULT_LOCALE = "en_US";
+    private static final String DEFAULT_MODE = "keyboard";
+
+    public static final Class<?> CLASS_InputMethodSubtype =
+            CompatUtils.getClass("android.view.inputmethod.InputMethodSubtype");
+    private static final Method METHOD_getNameResId =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getNameResId");
+    private static final Method METHOD_getIconResId =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getIconResId");
+    private static final Method METHOD_getLocale =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getLocale");
+    private static final Method METHOD_getMode =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getMode");
+    private static final Method METHOD_getExtraValue =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValue");
+    private static final Method METHOD_containsExtraValueKey =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "containsExtraValueKey", String.class);
+    private static final Method METHOD_getExtraValueOf =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class);
+
+    public InputMethodSubtypeCompatWrapper(Object subtype) {
+        super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype))
+                ? subtype : null);
+        if (DBG) {
+            Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper");
+        }
+    }
+
+    public int getNameResId() {
+        return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId);
+    }
+
+    public int getIconResId() {
+        return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId);
+    }
+
+    public String getLocale() {
+        final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale);
+        if (TextUtils.isEmpty(s)) return DEFAULT_LOCALE;
+        return s;
+    }
+
+    public String getMode() {
+        String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode);
+        if (TextUtils.isEmpty(s)) return DEFAULT_MODE;
+        return s;
+    }
+
+    public String getExtraValue() {
+        return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue);
+    }
+
+    public boolean containsExtraValueKey(String key) {
+        return (Boolean)CompatUtils.invoke(mObj, false, METHOD_containsExtraValueKey, key);
+    }
+
+    public String getExtraValueOf(String key) {
+        return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValueOf, key);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof InputMethodSubtypeCompatWrapper) {
+            InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o;
+            return mObj.equals(subtype.getOriginalObject());
+        } else {
+            return mObj.equals(o);
+        }
+    }
+
+}
diff --git a/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java
new file mode 100644
index 0000000..d851741
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.text.InputType;
+
+import java.lang.reflect.Field;
+
+public class InputTypeCompatUtils {
+    private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS =
+            CompatUtils.getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
+    private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = CompatUtils
+            .getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_PASSWORD");
+    private static final Field FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD = CompatUtils
+            .getField(InputType.class, "TYPE_NUMBER_VARIATION_PASSWORD");
+    private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS =
+            (Integer) CompatUtils.getFieldValue(null, null,
+                    FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
+    private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD =
+            (Integer) CompatUtils.getFieldValue(null, null,
+                    FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD);
+    private static final Integer OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD =
+            (Integer) CompatUtils.getFieldValue(null, null,
+                    FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD);
+    private static final int WEB_TEXT_PASSWORD_INPUT_TYPE;
+    private static final int NUMBER_PASSWORD_INPUT_TYPE;
+    private static final int TEXT_PASSWORD_INPUT_TYPE =
+            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
+    private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE =
+            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+
+    static {
+        WEB_TEXT_PASSWORD_INPUT_TYPE =
+                OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD != null
+                        ? InputType.TYPE_CLASS_TEXT | OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD
+                        : 0;
+        NUMBER_PASSWORD_INPUT_TYPE =
+                OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD != null
+                        ? InputType.TYPE_CLASS_NUMBER | OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD
+                        : 0;
+    }
+
+    private static boolean isWebPasswordInputType(int inputType) {
+        return WEB_TEXT_PASSWORD_INPUT_TYPE != 0
+                && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isNumberPasswordInputType(int inputType) {
+        return NUMBER_PASSWORD_INPUT_TYPE != 0
+                && inputType == NUMBER_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isTextPasswordInputType(int inputType) {
+        return inputType == TEXT_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isWebEmailAddressVariation(int variation) {
+        return OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != null
+                && variation == OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+    }
+
+    public static boolean isEmailVariation(int variation) {
+        return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                || isWebEmailAddressVariation(variation);
+    }
+
+    // Please refer to TextView.isPasswordInputType
+    public static boolean isPasswordInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+        return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType)
+                || isNumberPasswordInputType(maskedInputType);
+    }
+
+    // Please refer to TextView.isVisiblePasswordInputType
+    public static boolean isVisiblePasswordInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+        return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
new file mode 100644
index 0000000..8e2a2e0
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Context;
+import android.os.Vibrator;
+
+import java.lang.reflect.Method;
+
+public class VibratorCompatWrapper {
+    private static final Method METHOD_hasVibrator = CompatUtils.getMethod(Vibrator.class,
+            "hasVibrator", int.class);
+
+    private static final VibratorCompatWrapper sInstance = new VibratorCompatWrapper();
+    private Vibrator mVibrator;
+
+    private VibratorCompatWrapper() {
+    }
+
+    public static VibratorCompatWrapper getInstance(Context context) {
+        if (sInstance.mVibrator == null) {
+            sInstance.mVibrator =
+                    (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+        }
+        return sInstance;
+    }
+
+    public boolean hasVibrator() {
+        if (mVibrator == null)
+            return false;
+        return (Boolean) CompatUtils.invoke(mVibrator, true, METHOD_hasVibrator);
+    }
+}
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
similarity index 85%
rename from java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
rename to java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index 105656f..5fba29d 100644
--- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -14,8 +14,14 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated;
 
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.deprecated.voice.FieldContext;
+import com.android.inputmethod.deprecated.voice.Hints;
+import com.android.inputmethod.deprecated.voice.SettingsUtil;
+import com.android.inputmethod.deprecated.voice.VoiceInput;
+import com.android.inputmethod.deprecated.voice.VoiceInputLogger;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.latin.EditingUtils;
 import com.android.inputmethod.latin.LatinIME;
@@ -28,6 +34,7 @@
 import com.android.inputmethod.latin.Utils;
 
 import android.app.AlertDialog;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -54,7 +61,6 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -62,8 +68,8 @@
 import java.util.List;
 import java.util.Map;
 
-public class VoiceIMEConnector implements VoiceInput.UiListener {
-    private static final VoiceIMEConnector sInstance = new VoiceIMEConnector();
+public class VoiceProxy implements VoiceInput.UiListener {
+    private static final VoiceProxy sInstance = new VoiceProxy();
 
     public static final boolean VOICE_INSTALLED = true;
     private static final boolean ENABLE_VOICE_BUTTON = true;
@@ -77,7 +83,7 @@
             "has_used_voice_input_unsupported_locale";
     private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
 
-    private static final String TAG = VoiceIMEConnector.class.getSimpleName();
+    private static final String TAG = VoiceProxy.class.getSimpleName();
     private static final boolean DEBUG = LatinImeLogger.sDBG;
 
     private boolean mAfterVoiceInput;
@@ -93,7 +99,7 @@
     private boolean mVoiceButtonOnPrimary;
     private boolean mVoiceInputHighlighted;
 
-    private InputMethodManager mImm;
+    private InputMethodManagerCompatWrapper mImm;
     private LatinIME mService;
     private AlertDialog mVoiceWarningDialog;
     private VoiceInput mVoiceInput;
@@ -105,19 +111,19 @@
     private final Map<String, List<CharSequence>> mWordToSuggestions =
             new HashMap<String, List<CharSequence>>();
 
-    public static VoiceIMEConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) {
+    public static VoiceProxy init(LatinIME context, SharedPreferences prefs, UIHandler h) {
         sInstance.initInternal(context, prefs, h);
         return sInstance;
     }
 
-    public static VoiceIMEConnector getInstance() {
+    public static VoiceProxy getInstance() {
         return sInstance;
     }
 
     private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) {
         mService = service;
         mHandler = h;
-        mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+        mImm = InputMethodManagerCompatWrapper.getInstance(service);
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         if (VOICE_INSTALLED) {
             mVoiceInput = new VoiceInput(service, this);
@@ -133,7 +139,7 @@
         }
     }
 
-    private VoiceIMEConnector() {
+    private VoiceProxy() {
         // Intentional empty constructor for singleton.
     }
 
@@ -560,14 +566,24 @@
 
             @Override
             protected void onPostExecute(Boolean result) {
+                // Calls in this method need to be done in the same thread as the thread which
+                // called switchToLastInputMethod()
                 if (!result) {
                     if (DEBUG) {
                         Log.d(TAG, "Couldn't switch back to last IME.");
                     }
-                    // Needs to reset here because LatinIME failed to back to any IME and
-                    // the same voice subtype will be triggered in the next time.
+                    // Because the current IME and subtype failed to switch to any other IME and
+                    // subtype by switchToLastInputMethod, the current IME and subtype should keep
+                    // being LatinIME and voice subtype in the next time. And for re-showing voice
+                    // mode, the state of voice input should be reset and the voice view should be
+                    // hidden.
                     mVoiceInput.reset();
                     mService.requestHideSelf(0);
+                } else {
+                    // Notify an event that the current subtype was changed. This event will be
+                    // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
+                    // when the API level is 10 or previous.
+                    mService.notifyOnCurrentInputMethodSubtypeChanged(null);
                 }
             }
         }.execute();
@@ -624,6 +640,7 @@
     }
 
     private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+        @SuppressWarnings("deprecation")
         final boolean noMic = Utils.inPrivateImeOptions(null,
                 LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute)
                 || Utils.inPrivateImeOptions(mService.getPackageName(),
@@ -674,7 +691,7 @@
     public void onAttachedToWindow() {
         // After onAttachedToWindow, we can show the voice warning dialog. See startListening()
         // above.
-        mSubtypeSwitcher.setVoiceInput(mVoiceInput);
+        VoiceInputWrapper.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher);
     }
 
     public void onConfigurationChanged(Configuration configuration) {
@@ -725,4 +742,91 @@
         List<String> candidates;
         Map<String, List<CharSequence>> alternatives;
     }
+
+    public static class VoiceLoggerWrapper {
+        private static final VoiceLoggerWrapper sInstance = new VoiceLoggerWrapper();
+        private VoiceInputLogger mLogger;
+
+        public static VoiceLoggerWrapper getInstance(Context context) {
+            if (sInstance.mLogger == null) {
+                // Not thread safe, but it's ok.
+                sInstance.mLogger = VoiceInputLogger.getLogger(context);
+            }
+            return sInstance;
+        }
+
+        // private for the singleton
+        private VoiceLoggerWrapper() {
+        }
+
+        public void settingsWarningDialogCancel() {
+            mLogger.settingsWarningDialogCancel();
+        }
+
+        public void settingsWarningDialogOk() {
+            mLogger.settingsWarningDialogOk();
+        }
+
+        public void settingsWarningDialogShown() {
+            mLogger.settingsWarningDialogShown();
+        }
+
+        public void settingsWarningDialogDismissed() {
+            mLogger.settingsWarningDialogDismissed();
+        }
+
+        public void voiceInputSettingEnabled(boolean enabled) {
+            if (enabled) {
+                mLogger.voiceInputSettingEnabled();
+            } else {
+                mLogger.voiceInputSettingDisabled();
+            }
+        }
+    }
+
+    public static class VoiceInputWrapper {
+        private static final VoiceInputWrapper sInstance = new VoiceInputWrapper();
+        private VoiceInput mVoiceInput;
+        public static VoiceInputWrapper getInstance() {
+            return sInstance;
+        }
+        public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) {
+            if (mVoiceInput == null && voiceInput != null) {
+                mVoiceInput = voiceInput;
+            }
+            switcher.setVoiceInputWrapper(this);
+        }
+
+        private VoiceInputWrapper() {
+        }
+
+        public void cancel() {
+            if (mVoiceInput != null) mVoiceInput.cancel();
+        }
+
+        public void reset() {
+            if (mVoiceInput != null) mVoiceInput.reset();
+        }
+    }
+
+    // A list of locales which are supported by default for voice input, unless we get a
+    // different list from Gservices.
+    private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
+            "en " +
+            "en_US " +
+            "en_GB " +
+            "en_AU " +
+            "en_CA " +
+            "en_IE " +
+            "en_IN " +
+            "en_NZ " +
+            "en_SG " +
+            "en_ZA ";
+
+    public static String getSupportedLocalesString (ContentResolver resolver) {
+        return SettingsUtil.getSettingsString(
+                resolver,
+                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
+                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+    }
 }
diff --git a/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java
new file mode 100644
index 0000000..488390f
--- /dev/null
+++ b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.deprecated.compat;
+
+import com.android.common.userhappiness.UserHappinessSignals;
+import com.android.inputmethod.compat.CompatUtils;
+
+import java.lang.reflect.Method;
+
+public class VoiceInputLoggerCompatUtils {
+    public static final String EXTRA_TEXT_REPLACED_LENGTH = "length";
+    public static final String EXTRA_BEFORE_N_BEST_CHOOSE = "before";
+    public static final String EXTRA_AFTER_N_BEST_CHOOSE = "after";
+    private static final Method METHOD_UserHappinessSignals_setHasVoiceLoggingInfo =
+            CompatUtils.getMethod(UserHappinessSignals.class, "setHasVoiceLoggingInfo",
+                    boolean.class);
+
+    public static void setHasVoiceLoggingInfoCompat(boolean hasLoggingInfo) {
+        CompatUtils.invoke(null, null, METHOD_UserHappinessSignals_setHasVoiceLoggingInfo,
+                hasLoggingInfo);
+    }
+}
diff --git a/java/src/com/android/inputmethod/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/FieldContext.java
rename to java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
index dfdfbaa..0ef73d2 100644
--- a/java/src/com/android/inputmethod/voice/FieldContext.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.os.Bundle;
 import android.util.Log;
diff --git a/java/src/com/android/inputmethod/voice/Hints.java b/java/src/com/android/inputmethod/deprecated/voice/Hints.java
similarity index 99%
rename from java/src/com/android/inputmethod/voice/Hints.java
rename to java/src/com/android/inputmethod/deprecated/voice/Hints.java
index d11d3b0..52a4f4e 100644
--- a/java/src/com/android/inputmethod/voice/Hints.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/Hints.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SharedPreferencesCompat;
diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
similarity index 99%
rename from java/src/com/android/inputmethod/voice/RecognitionView.java
rename to java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
index 95a79f4..52c73ce 100644
--- a/java/src/com/android/inputmethod/voice/RecognitionView.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
diff --git a/java/src/com/android/inputmethod/voice/SettingsUtil.java b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/SettingsUtil.java
rename to java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
index 4d746e1..7721fe2 100644
--- a/java/src/com/android/inputmethod/voice/SettingsUtil.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.content.ContentResolver;
 import android.provider.Settings;
diff --git a/java/src/com/android/inputmethod/voice/SoundIndicator.java b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/SoundIndicator.java
rename to java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
index 543290b..8cc79de 100644
--- a/java/src/com/android/inputmethod/voice/SoundIndicator.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.content.Context;
 import android.graphics.Bitmap;
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
similarity index 99%
rename from java/src/com/android/inputmethod/voice/VoiceInput.java
rename to java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
index 2df9e85..7ee0de9 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.inputmethod.latin.EditingUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
similarity index 95%
rename from java/src/com/android/inputmethod/voice/VoiceInputLogger.java
rename to java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
index 3e65434..dcd124f 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
@@ -14,10 +14,11 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.common.speech.LoggingEvents;
 import com.android.common.userhappiness.UserHappinessSignals;
+import com.android.inputmethod.deprecated.compat.VoiceInputLoggerCompatUtils;
 
 import android.content.Context;
 import android.content.Intent;
@@ -212,13 +213,12 @@
         setHasLoggingInfo(true);
         Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
         i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
+        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
         i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
                 LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION);
-
         i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after);
+        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_BEFORE_N_BEST_CHOOSE, before);
+        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_AFTER_N_BEST_CHOOSE, after);
         mContext.sendBroadcast(i);
     }
 
@@ -257,7 +257,7 @@
         // 2. type subject in subject field
         // 3. speak message in message field
         // 4. press send
-        UserHappinessSignals.setHasVoiceLoggingInfo(hasLoggingInfo);
+        VoiceInputLoggerCompatUtils.setHasVoiceLoggingInfoCompat(hasLoggingInfo);
     }
 
     private boolean hasLoggingInfo(){
diff --git a/java/src/com/android/inputmethod/voice/WaveformImage.java b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/WaveformImage.java
rename to java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
index 8bac669..a3025f2 100644
--- a/java/src/com/android/inputmethod/voice/WaveformImage.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
diff --git a/java/src/com/android/inputmethod/voice/Whitelist.java b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
similarity index 97%
rename from java/src/com/android/inputmethod/voice/Whitelist.java
rename to java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
index f4c24de..310689c 100644
--- a/java/src/com/android/inputmethod/voice/Whitelist.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.os.Bundle;
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index d09f678..f68b68f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -16,8 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
 
 import android.view.inputmethod.EditorInfo;
 
@@ -62,8 +63,8 @@
         this.mMode = mode;
         this.mXmlId = xmlId;
         this.mColorScheme = colorScheme;
-        this.mPasswordInput = Utils.isPasswordInputType(inputType)
-                || Utils.isVisiblePasswordInputType(inputType);
+        this.mPasswordInput = InputTypeCompatUtils.isPasswordInputType(inputType)
+                || InputTypeCompatUtils.isVisiblePasswordInputType(inputType);
         this.mHasSettingsKey = hasSettingsKey;
         this.mVoiceKeyEnabled = voiceKeyEnabled;
         this.mHasVoiceKey = hasVoiceKey;
@@ -140,7 +141,7 @@
                 mLocale,
                 (mOrientation == 1 ? "port" : "land"),
                 modeName(mMode),
-                imeOptionsName(mImeAction),
+                EditorInfoCompatUtils.imeOptionsName(mImeAction),
                 (mPasswordInput ? " passwordInput" : ""),
                 (mHasSettingsKey ? " hasSettingsKey" : ""),
                 (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""),
@@ -170,26 +171,4 @@
         }
         return null;
     }
-
-    public static String imeOptionsName(int imeOptions) {
-        if (imeOptions == -1) return null;
-        final int actionNo = imeOptions & EditorInfo.IME_MASK_ACTION;
-        final String action;
-        switch (actionNo) {
-        case EditorInfo.IME_ACTION_UNSPECIFIED: action = "actionUnspecified"; break;
-        case EditorInfo.IME_ACTION_NONE: action = "actionNone"; break;
-        case EditorInfo.IME_ACTION_GO: action = "actionGo"; break;
-        case EditorInfo.IME_ACTION_SEARCH: action = "actionSearch"; break;
-        case EditorInfo.IME_ACTION_SEND: action = "actionSend"; break;
-        case EditorInfo.IME_ACTION_DONE: action = "actionDone"; break;
-        case EditorInfo.IME_ACTION_PREVIOUS: action = "actionPrevious"; break;
-        default: action = "actionUnknown(" + actionNo + ")"; break;
-        }
-        if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
-            return "flagNoEnterAction|" + action;
-        } else {
-            return action;
-        }
-    }
 }
-
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
index feb56ab..62e6f30 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.latin.R;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -453,7 +454,7 @@
                     booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
                     booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"),
                     booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
-                    textAttr(KeyboardId.imeOptionsName(
+                    textAttr(EditorInfoCompatUtils.imeOptionsName(
                             a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
                     textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"),
                     textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 64a23ab..cfa3c44 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
@@ -29,7 +30,6 @@
 import android.util.Log;
 import android.view.InflateException;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
 
 import java.lang.ref.SoftReference;
 import java.util.HashMap;
@@ -752,8 +752,7 @@
             if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
                     || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO))
                             && Utils.hasMultipleEnabledIMEsOrSubtypes(
-                                    ((InputMethodManager) context.getSystemService(
-                                            Context.INPUT_METHOD_SERVICE))))) {
+                                    (InputMethodManagerCompatWrapper.getInstance(context))))) {
                 return true;
             }
             return false;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 77e9cae..d6c3723 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,9 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.Utils;
-import com.android.inputmethod.voice.VoiceIMEConnector;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -264,6 +264,6 @@
     @Override
     protected void onAttachedToWindow() {
         // Token is available from here.
-        VoiceIMEConnector.getInstance().onAttachedToWindow();
+        VoiceProxy.getInstance().onAttachedToWindow();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
new file mode 100644
index 0000000..074ecac
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.io.File;
+
+/**
+ * Immutable class to hold the address of an asset.
+ * As opposed to a normal file, an asset is usually represented as a contiguous byte array in
+ * the package file. Open it correctly thus requires the name of the package it is in, but
+ * also the offset in the file and the length of this data. This class encapsulates these three.
+ */
+class AssetFileAddress {
+    public final String mFilename;
+    public final long mOffset;
+    public final long mLength;
+
+    public AssetFileAddress(final String filename, final long offset, final long length) {
+        mFilename = filename;
+        mOffset = offset;
+        mLength = length;
+    }
+
+    public static AssetFileAddress makeFromFileName(final String filename) {
+        if (null == filename) return null;
+        File f = new File(filename);
+        if (null == f || !f.isFile()) return null;
+        return new AssetFileAddress(filename, 0l, f.length());
+    }
+
+    public static AssetFileAddress makeFromFileNameAndOffset(final String filename,
+            final long offset, final long length) {
+        if (null == filename) return null;
+        File f = new File(filename);
+        if (null == f || !f.isFile()) return null;
+        return new AssetFileAddress(filename, offset, length);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 092f7ad..d311979 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -48,7 +48,7 @@
     }
 
     public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
-            WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] priorities,
+            WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
             CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
             CharSequence quickFixedWord, CharSequence whitelistedWord) {
         if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
@@ -62,7 +62,7 @@
             mHasAutoCorrection = true;
             mAutoCorrectionWord = quickFixedWord;
         } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
-                priorities, typedWord, autoCorrectionThreshold)) {
+                sortedScores, typedWord, autoCorrectionThreshold)) {
             mHasAutoCorrection = true;
             mAutoCorrectionWord = suggestions.get(0);
         }
@@ -114,13 +114,13 @@
     }
 
     private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
-            ArrayList<CharSequence> suggestions, int correctionMode, int[] priorities,
+            ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores,
             CharSequence typedWord, double autoCorrectionThreshold) {
         if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
                 || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
-                && typedWord != null && suggestions.size() > 0 && priorities.length > 0) {
+                && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) {
             final CharSequence autoCorrectionCandidate = suggestions.get(0);
-            final int autoCorrectionCandidateScore = priorities[0];
+            final int autoCorrectionCandidateScore = sortedScores[0];
             // TODO: when the normalized score of the first suggestion is nearly equals to
             //       the normalized score of the second suggestion, behave less aggressive.
             mNormalizedScore = Utils.calcNormalizedScore(
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 08ddd25..fa90fce 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -26,14 +26,18 @@
 
 import java.io.File;
 import java.util.Arrays;
+import java.util.Locale;
 
 /**
  * Implements a static, compacted, binary dictionary of standard words.
  */
 public class BinaryDictionary extends Dictionary {
 
+    public static final String DICTIONARY_PACK_AUTHORITY =
+            "com.android.inputmethod.latin.dictionarypack";
+
     /**
-     * There is difference between what java and native code can handle.
+     * There is a difference between what java and native code can handle.
      * This value should only be used in BinaryDictionary.java
      * It is necessary to keep it at this value because some languages e.g. German have
      * really long words.
@@ -54,38 +58,42 @@
     private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
     private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
     private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
-    private final int[] mFrequencies = new int[MAX_WORDS];
-    private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
+    private final int[] mScores = new int[MAX_WORDS];
+    private final int[] mBigramScores = new int[MAX_BIGRAMS];
 
     private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance();
-    private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
 
-    private static class Flags {
-        private static class FlagEntry {
-            public final String mName;
-            public final int mValue;
-            public FlagEntry(String name, int value) {
-                mName = name;
-                mValue = value;
-            }
+    public static class Flag {
+        public final String mName;
+        public final int mValue;
+
+        public Flag(String name, int value) {
+            mName = name;
+            mValue = value;
         }
-        public static final FlagEntry[] ALL_FLAGS = {
-            // Here should reside all flags that trigger some special processing
-            // These *must* match the definition in UnigramDictionary enum in
-            // unigram_dictionary.h so please update both at the same time.
-            new FlagEntry("requiresGermanUmlautProcessing", 0x1)
-        };
     }
+
+    public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
+            new Flag("requiresGermanUmlautProcessing", 0x1);
+
+    private static final Flag[] ALL_FLAGS = {
+        // Here should reside all flags that trigger some special processing
+        // These *must* match the definition in UnigramDictionary enum in
+        // unigram_dictionary.h so please update both at the same time.
+        FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+    };
+
     private int mFlags = 0;
 
     private BinaryDictionary() {
     }
 
     /**
-     * Initialize a dictionary from a raw resource file
+     * Initializes a dictionary from a raw resource file
      * @param context application context for reading resources
      * @param resId the resource containing the raw binary dictionary
-     * @return initialized instance of BinaryDictionary
+     * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
+     * @return an initialized instance of BinaryDictionary
      */
     public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
         synchronized (sInstance) {
@@ -110,12 +118,12 @@
                 return null;
             }
         }
-        sInstance.initFlags();
+        sInstance.mFlags = initFlags(ALL_FLAGS, SubtypeSwitcher.getInstance());
         return sInstance;
     }
 
     /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
-            long length, int dicTypeId) {
+            long length, int dicTypeId, Flag[] flagArray) {
         synchronized (sInstance) {
             sInstance.closeInternal();
             if (dictionary.isFile()) {
@@ -126,22 +134,54 @@
                 return null;
             }
         }
+        sInstance.mFlags = initFlags(flagArray, null);
         return sInstance;
     }
 
-    private void initFlags() {
+    private static int initFlags(Flag[] flagArray, SubtypeSwitcher switcher) {
         int flags = 0;
-        for (Flags.FlagEntry entry : Flags.ALL_FLAGS) {
-            if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName))
+        for (Flag entry : flagArray) {
+            if (switcher == null || switcher.currentSubtypeContainsExtraValueKey(entry.mName))
                 flags |= entry.mValue;
         }
-        mFlags = flags;
+        return flags;
     }
 
     static {
         Utils.loadNativeLibrary();
     }
 
+    /**
+     * Initializes a dictionary from a dictionary pack.
+     *
+     * This searches for a content provider providing a dictionary pack for the specified
+     * locale. If none is found, it falls back to using the resource passed as fallBackResId
+     * as a dictionary.
+     * @param context application context for reading resources
+     * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
+     * @param locale the locale for which to create the dictionary
+     * @param fallBackResId the id of the resource to use as a fallback if no pack is found
+     * @return an initialized instance of BinaryDictionary
+     */
+    public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId,
+            Locale locale, int fallbackResId) {
+        if (null == locale) {
+            Log.e(TAG, "No locale defined for dictionary");
+            return initDictionary(context, fallbackResId, dicTypeId);
+        }
+        synchronized (sInstance) {
+            sInstance.closeInternal();
+
+            final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale,
+                    context, fallbackResId);
+            if (null != dictFile) {
+                sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength);
+                sInstance.mDicTypeId = dicTypeId;
+            }
+        }
+        return sInstance;
+    }
+
     private native int openNative(String sourceDir, long dictOffset, long dictSize,
             int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
             int maxWords, int maxAlternatives);
@@ -149,14 +189,14 @@
     private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
     private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
             int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
-            int[] frequencies);
+            int[] scores);
     private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
-            int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
+            int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
             int maxWordLength, int maxBigrams, int maxAlternatives);
 
     private final void loadDictionary(String path, long startOffset, long length) {
         mNativeDict = openNative(path, startOffset, length,
-                    TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
+                    TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
                     MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
         mDictLength = length;
     }
@@ -168,7 +208,7 @@
 
         char[] chars = previousWord.toString().toCharArray();
         Arrays.fill(mOutputChars_bigrams, (char) 0);
-        Arrays.fill(mFrequencies_bigrams, 0);
+        Arrays.fill(mBigramScores, 0);
 
         int codesSize = codes.size();
         Arrays.fill(mInputCodes, -1);
@@ -177,18 +217,18 @@
                 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
 
         int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
-                mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS,
+                mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
                 MAX_PROXIMITY_CHARS_SIZE);
 
         for (int j = 0; j < count; ++j) {
-            if (mFrequencies_bigrams[j] < 1) break;
+            if (mBigramScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
             while (len <  MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j],
+                callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
                         mDicTypeId, DataType.BIGRAM);
             }
         }
@@ -197,17 +237,17 @@
     @Override
     public void getWords(final WordComposer codes, final WordCallback callback) {
         final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
-                mOutputChars, mFrequencies);
+                mOutputChars, mScores);
 
         for (int j = 0; j < count; ++j) {
-            if (mFrequencies[j] < 1) break;
+            if (mScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
             while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId,
+                callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
                         DataType.UNIGRAM);
             }
         }
@@ -218,7 +258,7 @@
     }
 
     /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
-            char[] outputChars, int[] frequencies) {
+            char[] outputChars, int[] scores) {
         if (!isValidDictionary()) return -1;
 
         final int codesSize = codes.size();
@@ -232,12 +272,12 @@
                     Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
         }
         Arrays.fill(outputChars, (char) 0);
-        Arrays.fill(frequencies, 0);
+        Arrays.fill(scores, 0);
 
         return getSuggestionsNative(
                 mNativeDict, keyboard.getProximityInfo(),
                 codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
-                mFlags, outputChars, frequencies);
+                mFlags, outputChars, scores);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
new file mode 100644
index 0000000..d0464dd
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * Group class for static methods to help with creation and getting of the binary dictionary
+ * file from the dictionary provider
+ */
+public class BinaryDictionaryFileDumper {
+    /**
+     * The size of the temporary buffer to copy files.
+     */
+    static final int FILE_READ_BUFFER_SIZE = 1024;
+
+    // Prevents this class to be accidentally instantiated.
+    private BinaryDictionaryFileDumper() {
+    }
+
+    /**
+     * Generates a file name that matches the locale passed as an argument.
+     * The file name is basically the result of the .toString() method, except we replace
+     * any @File.separator with an underscore to avoid generating a file name that may not
+     * be created.
+     * @param locale the locale for which to get the file name
+     * @param context the context to use for getting the directory
+     * @return the name of the file to be created
+     */
+    private static String getCacheFileNameForLocale(Locale locale, Context context) {
+        // The following assumes two things :
+        // 1. That File.separator is not the same character as "_"
+        //    I don't think any android system will ever use "_" as a path separator
+        // 2. That no two locales differ by only a File.separator versus a "_"
+        //    Since "_" can't be part of locale components this should be safe.
+        // Examples:
+        // en -> en
+        // en_US_POSIX -> en_US_POSIX
+        // en__foo/bar -> en__foo_bar
+        final String[] separator = { File.separator };
+        final String[] empty = { "_" };
+        final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty);
+        return context.getFilesDir() + File.separator + basename;
+    }
+
+    /**
+     * Return for a given locale the provider URI to query to get the dictionary.
+     */
+    public static Uri getProviderUri(Locale locale) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
+                        locale.toString()).build();
+    }
+
+    /**
+     * Queries a content provider for dictionary data for some locale and returns it as a file name.
+     *
+     * This will query a content provider for dictionary data for a given locale, and return
+     * the name of a file suitable to be mmap'ed. It will copy it to local storage if needed.
+     * It should also check the dictionary version to avoid unnecessary copies but this is
+     * still in TODO state.
+     * This will make the data from the content provider the cached dictionary for this locale,
+     * overwriting any previous cached data.
+     * @returns the name of the file, or null if no data could be obtained.
+     * @throw FileNotFoundException if the provider returns non-existent data.
+     * @throw IOException if the provider-returned data could not be read.
+     */
+    public static String getDictionaryFileFromContentProvider(Locale locale, Context context)
+            throws FileNotFoundException, IOException {
+        // TODO: check whether the dictionary is the same or not and if it is, return the cached
+        // file.
+        final ContentResolver resolver = context.getContentResolver();
+        final Uri dictionaryPackUri = getProviderUri(locale);
+        final InputStream stream = resolver.openInputStream(dictionaryPackUri);
+        if (null == stream) return null;
+        return copyFileTo(stream, getCacheFileNameForLocale(locale, context));
+    }
+
+    /**
+     * Accepts a file as dictionary data for some locale and returns the name of a file.
+     *
+     * This will make the data in the input file the cached dictionary for this locale, overwriting
+     * any previous cached data.
+     */
+    public static String getDictionaryFileFromFile(String fileName, Locale locale,
+            Context context) throws FileNotFoundException, IOException {
+        return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale,
+                context));
+    }
+
+    /**
+     * Accepts a resource number as dictionary data for some locale and returns the name of a file.
+     *
+     * This will make the resource the cached dictionary for this locale, overwriting any previous
+     * cached data.
+     */
+    public static String getDictionaryFileFromResource(int resource, Locale locale,
+            Context context) throws FileNotFoundException, IOException {
+        return copyFileTo(context.getResources().openRawResource(resource),
+                getCacheFileNameForLocale(locale, context));
+    }
+
+    /**
+     * Copies the data in an input stream to a target file, creating the file if necessary and
+     * overwriting it if it already exists.
+     */
+    private static String copyFileTo(final InputStream input, final String outputFileName)
+            throws FileNotFoundException, IOException {
+        final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
+        final FileOutputStream output = new FileOutputStream(outputFileName);
+        for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+            output.write(buffer, 0, readBytes);
+        input.close();
+        return outputFileName;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
new file mode 100644
index 0000000..72512c7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * Helper class to get the address of a mmap'able dictionary file.
+ */
+class BinaryDictionaryGetter {
+
+    /**
+     * Used for Log actions from this class
+     */
+    private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
+
+    // Prevents this from being instantiated
+    private BinaryDictionaryGetter() {}
+
+    /**
+     * Returns a file address from a resource, or null if it cannot be opened.
+     */
+    private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) {
+        final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
+        if (afd == null) {
+            Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
+                    + fallbackResId);
+            return null;
+        }
+        return AssetFileAddress.makeFromFileNameAndOffset(
+                context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
+    }
+
+    /**
+     * Returns a file address for a given locale, trying relevant methods in order.
+     *
+     * Tries to get a binary dictionary from various sources, in order:
+     * - Uses a private method of getting a private dictionary, as implemented by the
+     *   PrivateBinaryDictionaryGetter class.
+     * If that fails:
+     * - Uses a content provider to get a public dictionary, as per the protocol described
+     *   in BinaryDictionaryFileDumper.
+     * If that fails:
+     * - Gets a file name from the fallback resource passed as an argument.
+     * If that fails:
+     * - Returns null.
+     * @return The address of a valid file, or null.
+     * @throws FileNotFoundException if a dictionary provider returned a file name, but the
+     *                               file cannot be found.
+     * @throws IOException if there was an I/O problem reading or copying a file.
+     */
+    public static AssetFileAddress getDictionaryFile(Locale locale, Context context,
+            int fallbackResId) {
+        // Try first to query a private file signed the same way.
+        final AssetFileAddress privateFile =
+                PrivateBinaryDictionaryGetter.getDictionaryFile(locale, context);
+        if (null != privateFile) {
+            return privateFile;
+        } else {
+            try {
+                // If that was no-go, try to find a publicly exported dictionary.
+                final String fileName = BinaryDictionaryFileDumper.
+                        getDictionaryFileFromContentProvider(locale, context);
+                return AssetFileAddress.makeFromFileName(fileName);
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "Unable to create dictionary file from provider for locale "
+                        + locale.toString() + ": falling back to internal dictionary");
+                return loadFallbackResource(context, fallbackResId);
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to read source data for locale "
+                        + locale.toString() + ": falling back to internal dictionary");
+                return loadFallbackResource(context, fallbackResId);
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 56f0cc5..ac43d64 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -29,7 +29,7 @@
     /**
      * The weight to give to a word if it's length is the same as the number of typed characters.
      */
-    protected static final int FULL_WORD_FREQ_MULTIPLIER = 2;
+    protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
 
     public static enum DataType {
         UNIGRAM, BIGRAM
@@ -42,17 +42,17 @@
     public interface WordCallback {
         /**
          * Adds a word to a list of suggestions. The word is expected to be ordered based on
-         * the provided frequency.
+         * the provided score.
          * @param word the character array containing the word
          * @param wordOffset starting offset of the word in the character array
          * @param wordLength length of valid characters in the character array
-         * @param frequency the frequency of occurrence. This is normalized between 1 and 255, but
+         * @param score the score of occurrence. This is normalized between 1 and 255, but
          * can exceed those limits
          * @param dicTypeId of the dictionary where word was from
          * @param dataType tells type of this data
          * @return true if the word was added, false if no more words are required
          */
-        boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId,
+        boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
                 DataType dataType);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
new file mode 100644
index 0000000..c786462
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+
+/**
+ * Takes action to reload the necessary data when a dictionary pack was added/removed.
+ */
+public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+
+    final LatinIME mService;
+
+    public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
+        mService = service;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        final PackageManager manager = context.getPackageManager();
+
+        // We need to reread the dictionary if a new dictionary package is installed.
+        if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
+            final Uri packageUri = intent.getData();
+            if (null == packageUri) return; // No package name : we can't do anything
+            final String packageName = packageUri.getSchemeSpecificPart();
+            if (null == packageName) return;
+            final PackageInfo packageInfo;
+            try {
+                packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS);
+            } catch (android.content.pm.PackageManager.NameNotFoundException e) {
+                return; // No package info : we can't do anything
+            }
+            final ProviderInfo[] providers = packageInfo.providers;
+            if (null == providers) return; // No providers : it is not a dictionary.
+
+            // Search for some dictionary pack in the just-installed package. If found, reread.
+            for (ProviderInfo info : providers) {
+                if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) {
+                    mService.resetSuggestMainDict();
+                    return;
+                }
+            }
+            // If we come here none of the authorities matched the one we searched for.
+            // We can exit safely.
+            return;
+        } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+                && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+            // When the dictionary package is removed, we need to reread dictionary (to use the
+            // next-priority one, or stop using a dictionary at all if this was the only one,
+            // since this is the user request).
+            // If we are replacing the package, we will receive ADDED right away so no need to
+            // remove the dictionary at the moment, since we will do it when we receive the
+            // ADDED broadcast.
+
+            // TODO: Only reload dictionary on REMOVED when the removed package is the one we
+            // read dictionary from?
+            mService.resetSuggestMainDict();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0318175..d87fbce 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -327,7 +327,7 @@
                                     final int finalFreq;
                                     if (skipPos < 0) {
                                         finalFreq = freq * snr * addedAttenuation
-                                                * FULL_WORD_FREQ_MULTIPLIER;
+                                                * FULL_WORD_SCORE_MULTIPLIER;
                                     } else {
                                         finalFreq = computeSkippedWordFinalFreq(freq,
                                                 snr * addedAttenuation, mInputLength);
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index 5587c68..be5e015 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -106,8 +106,8 @@
         conf.locale = locale;
         res.updateConfiguration(conf, res.getDisplayMetrics());
 
-        int mainDicResId = Utils.getMainDictionaryResourceId(res);
-        BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN);
+        BinaryDictionary bd = BinaryDictionary.initDictionaryFromManager(this, Suggest.DIC_MAIN,
+                locale, Utils.getMainDictionaryResourceId(res));
 
         // Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
         // 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words.
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6e76cad..ae23154 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,6 +16,14 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
@@ -23,7 +31,6 @@
 import com.android.inputmethod.keyboard.LatinKeyboard;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
 import com.android.inputmethod.latin.Utils.RingCharBuffer;
-import com.android.inputmethod.voice.VoiceIMEConnector;
 
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
@@ -42,7 +49,6 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.SystemClock;
-import android.os.Vibrator;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceManager;
 import android.text.InputType;
@@ -61,13 +67,10 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
@@ -81,7 +84,7 @@
 /**
  * Input method implementation for Qwerty'ish keyboard.
  */
-public class LatinIME extends InputMethodService implements KeyboardActionListener {
+public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener {
     private static final String TAG = LatinIME.class.getSimpleName();
     private static final boolean PERF_DEBUG = false;
     private static final boolean TRACE = false;
@@ -94,6 +97,7 @@
      *
      * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed.
      */
+    @SuppressWarnings("dep-ann")
     public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm";
 
     /**
@@ -119,6 +123,12 @@
     // Key events coming any faster than this are long-presses.
     private static final int QUICK_PRESS = 200;
 
+    /**
+     * The name of the scheme used by the Package Manager to warn of a new package installation,
+     * replacement or removal.
+     */
+    private static final String SCHEME_PACKAGE = "package";
+
     private int mSuggestionVisibility;
     private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
             = R.string.prefs_suggestion_visibility_show_value;
@@ -140,13 +150,13 @@
 
     private AlertDialog mOptionsDialog;
 
-    private InputMethodManager mImm;
+    private InputMethodManagerCompatWrapper mImm;
     private Resources mResources;
     private SharedPreferences mPrefs;
     private String mInputMethodId;
     private KeyboardSwitcher mKeyboardSwitcher;
     private SubtypeSwitcher mSubtypeSwitcher;
-    private VoiceIMEConnector mVoiceConnector;
+    private VoiceProxy mVoiceProxy;
 
     private UserDictionary mUserDictionary;
     private UserBigramDictionary mUserBigramDictionary;
@@ -204,23 +214,37 @@
     /* package */ String mWordSeparators;
     private String mSentenceSeparators;
     private String mSuggestPuncs;
-    // TODO: Move this flag to VoiceIMEConnector
+    // TODO: Move this flag to VoiceProxy
     private boolean mConfigurationChanging;
 
+    // Object for reacting to adding/removing a dictionary pack.
+    private BroadcastReceiver mDictionaryPackInstallReceiver =
+            new DictionaryPackInstallBroadcastReceiver(this);
+
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
 
     private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
 
-    public abstract static class WordAlternatives {
-        protected CharSequence mChosenWord;
+    public class WordAlternatives {
+        private final CharSequence mChosenWord;
+        private final WordComposer mWordComposer;
 
-        public WordAlternatives() {
-            // Nothing
+        public WordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
+            mChosenWord = chosenWord;
+            mWordComposer = wordComposer;
         }
 
-        public WordAlternatives(CharSequence chosenWord) {
-            mChosenWord = chosenWord;
+        public CharSequence getChosenWord() {
+            return mChosenWord;
+        }
+
+        public CharSequence getOriginalWord() {
+            return mWordComposer.getTypedWord();
+        }
+
+        public SuggestedWords.Builder getAlternatives() {
+            return getTypedSuggestions(mWordComposer);
         }
 
         @Override
@@ -228,35 +252,9 @@
             return mChosenWord.hashCode();
         }
 
-        public abstract CharSequence getOriginalWord();
-
-        public CharSequence getChosenWord() {
-            return mChosenWord;
-        }
-
-        public abstract SuggestedWords.Builder getAlternatives();
-    }
-
-    public class TypedWordAlternatives extends WordAlternatives {
-        private WordComposer word;
-
-        public TypedWordAlternatives() {
-            // Nothing
-        }
-
-        public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
-            super(chosenWord);
-            word = wordComposer;
-        }
-
         @Override
-        public CharSequence getOriginalWord() {
-            return word.getTypedWord();
-        }
-
-        @Override
-        public SuggestedWords.Builder getAlternatives() {
-            return getTypedSuggestions(word);
+        public boolean equals(Object o) {
+            return o instanceof CharSequence && TextUtils.equals(mChosenWord, (CharSequence)o);
         }
     }
 
@@ -286,7 +284,7 @@
                 switcher.updateShiftState();
                 break;
             case MSG_VOICE_RESULTS:
-                mVoiceConnector.handleVoiceResults(preferCapitalization()
+                mVoiceProxy.handleVoiceResults(preferCapitalization()
                         || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
                 break;
             case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
@@ -385,7 +383,7 @@
 
         super.onCreate();
 
-        mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
+        mImm = InputMethodManagerCompatWrapper.getInstance(this);
         mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
@@ -430,18 +428,26 @@
         mOrientation = res.getConfiguration().orientation;
         initSuggestPuncList();
 
-        // register to receive ringer mode change and network state change.
+        // Register to receive ringer mode change and network state change.
+        // Also receive installation and removal of a dictionary pack.
         final IntentFilter filter = new IntentFilter();
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         registerReceiver(mReceiver, filter);
-        mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
+        mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
+
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme(SCHEME_PACKAGE);
+        registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
     }
 
     private void initSuggest() {
-        String locale = mSubtypeSwitcher.getInputLocaleStr();
+        final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+        final Locale keyboardLocale = new Locale(localeStr);
 
-        Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
+        final Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(keyboardLocale);
         if (mSuggest != null) {
             mSuggest.close();
         }
@@ -450,20 +456,20 @@
 
         final Resources res = mResources;
         int mainDicResId = Utils.getMainDictionaryResourceId(res);
-        mSuggest = new Suggest(this, mainDicResId);
+        mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
         loadAndSetAutoCorrectionThreshold(prefs);
         updateAutoTextEnabled();
 
-        mUserDictionary = new UserDictionary(this, locale);
+        mUserDictionary = new UserDictionary(this, localeStr);
         mSuggest.setUserDictionary(mUserDictionary);
 
         mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
         mSuggest.setContactsDictionary(mContactsDictionary);
 
-        mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO);
+        mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO);
         mSuggest.setAutoDictionary(mAutoDictionary);
 
-        mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER);
+        mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER);
         mSuggest.setUserBigramDictionary(mUserBigramDictionary);
 
         updateCorrectionMode();
@@ -473,6 +479,13 @@
         mSubtypeSwitcher.changeSystemLocale(savedLocale);
     }
 
+    /* package private */ void resetSuggestMainDict() {
+        final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+        final Locale keyboardLocale = new Locale(localeStr);
+        int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+        mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
+    }
+
     @Override
     public void onDestroy() {
         if (mSuggest != null) {
@@ -480,7 +493,8 @@
             mSuggest = null;
         }
         unregisterReceiver(mReceiver);
-        mVoiceConnector.destroy();
+        unregisterReceiver(mDictionaryPackInstallReceiver);
+        mVoiceProxy.destroy();
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -501,7 +515,7 @@
 
         mConfigurationChanging = true;
         super.onConfigurationChanged(conf);
-        mVoiceConnector.onConfigurationChanged(conf);
+        mVoiceProxy.onConfigurationChanged(conf);
         mConfigurationChanging = false;
     }
 
@@ -518,7 +532,7 @@
         if (container.getPaddingRight() != 0) {
             HorizontalScrollView scrollView =
                     (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view);
-            scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+            setOverScrollModeNever(scrollView);
             container.setGravity(Gravity.CENTER_HORIZONTAL);
         }
         mCandidateView = (CandidateView) container.findViewById(R.id.candidates);
@@ -547,9 +561,9 @@
         // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
         // know now whether this is a password text field, because we need to know now whether we
         // want to enable the voice button.
-        final VoiceIMEConnector voiceIme = mVoiceConnector;
-        voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType)
-                || Utils.isVisiblePasswordInputType(attribute.inputType));
+        final VoiceProxy voiceIme = mVoiceProxy;
+        voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType)
+                || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType));
 
         initializeInputAttributes(attribute);
 
@@ -603,17 +617,17 @@
         if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
             mIsSettingsSuggestionStripOn = true;
             // Make sure that passwords are not displayed in candidate view
-            if (Utils.isPasswordInputType(inputType)
-                    || Utils.isVisiblePasswordInputType(inputType)) {
+            if (InputTypeCompatUtils.isPasswordInputType(inputType)
+                    || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
                 mIsSettingsSuggestionStripOn = false;
             }
-            if (Utils.isEmailVariation(variation)
+            if (InputTypeCompatUtils.isEmailVariation(variation)
                     || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
                 mAutoSpace = false;
             } else {
                 mAutoSpace = true;
             }
-            if (Utils.isEmailVariation(variation)) {
+            if (InputTypeCompatUtils.isEmailVariation(variation)) {
                 mIsSettingsSuggestionStripOn = false;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
                 mIsSettingsSuggestionStripOn = false;
@@ -679,7 +693,7 @@
         LatinImeLogger.commit();
         mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
 
-        mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+        mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
 
         KeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (inputView != null) inputView.closing();
@@ -700,7 +714,7 @@
     @Override
     public void onUpdateExtractedText(int token, ExtractedText text) {
         super.onUpdateExtractedText(token, text);
-        mVoiceConnector.showPunctuationHintIfNecessary();
+        mVoiceProxy.showPunctuationHintIfNecessary();
     }
 
     @Override
@@ -721,7 +735,7 @@
                     + ", ce=" + candidatesEnd);
         }
 
-        mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
+        mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart);
 
         // If the current selection in the text view changes, we should
         // clear whatever candidate text we have.
@@ -729,7 +743,7 @@
                 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
         final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
         if (((mComposing.length() > 0 && mHasValidSuggestions)
-                || mVoiceConnector.isVoiceInputHighlighted())
+                || mVoiceProxy.isVoiceInputHighlighted())
                 && (selectionChanged || candidatesCleared)) {
             if (candidatesCleared) {
                 // If the composing span has been cleared, save the typed word in the history for
@@ -745,7 +759,7 @@
             if (ic != null) {
                 ic.finishComposingText();
             }
-            mVoiceConnector.setVoiceInputHighlighted(false);
+            mVoiceProxy.setVoiceInputHighlighted(false);
         } else if (!mHasValidSuggestions && !mJustAccepted) {
             if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) {
                 if (TextEntryState.isAcceptedDefault())
@@ -825,7 +839,7 @@
             mOptionsDialog.dismiss();
             mOptionsDialog = null;
         }
-        mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
+        mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
         mWordHistory.clear();
         super.hideWindow();
     }
@@ -902,8 +916,7 @@
             if (DEBUG) {
                 Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
             }
-            outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
-            outInsets.touchableRegion.set(x, y, width, height);
+            setTouchableRegionCompat(outInsets, x, y, width, height);
         }
     }
 
@@ -1176,7 +1189,7 @@
 
     @Override
     public void onTextInput(CharSequence text) {
-        mVoiceConnector.commitVoiceInput();
+        mVoiceProxy.commitVoiceInput();
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         abortRecorrection(false);
@@ -1198,13 +1211,13 @@
     }
 
     private void handleBackspace() {
-        if (mVoiceConnector.logAndRevertVoiceInput()) return;
+        if (mVoiceProxy.logAndRevertVoiceInput()) return;
 
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         ic.beginBatchEdit();
 
-        mVoiceConnector.handleBackspace();
+        mVoiceProxy.handleBackspace();
 
         boolean deleteChar = false;
         if (mHasValidSuggestions) {
@@ -1256,9 +1269,8 @@
 
     private void handleTab() {
         final int imeOptions = getCurrentInputEditorInfo().imeOptions;
-        final int navigationFlags =
-                EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
-        if ((imeOptions & navigationFlags) == 0) {
+        if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
+                && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
             return;
         }
@@ -1269,12 +1281,13 @@
 
         // True if keyboard is in either chording shift or manual temporary upper case mode.
         final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
-        if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
+        if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
                 && !isManualTemporaryUpperCase) {
+            EditorInfoCompatUtils.performEditorActionNext(ic);
             ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
-        } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
+        } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)
                 && isManualTemporaryUpperCase) {
-            ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+            EditorInfoCompatUtils.performEditorActionPrevious(ic);
         }
     }
 
@@ -1288,7 +1301,7 @@
     }
 
     private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
-        mVoiceConnector.handleCharacter();
+        mVoiceProxy.handleCharacter();
 
         if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
             abortRecorrection(false);
@@ -1348,7 +1361,7 @@
     }
 
     private void handleSeparator(int primaryCode) {
-        mVoiceConnector.handleSeparator();
+        mVoiceProxy.handleSeparator();
 
         // Should dismiss the "Touch again to save" message when handling separator
         if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
@@ -1400,11 +1413,8 @@
             CharSequence typedWord = mWord.getTypedWord();
             TextEntryState.backToAcceptedDefault(typedWord);
             if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
-                if (ic != null) {
-                    CorrectionInfo correctionInfo = new CorrectionInfo(
-                            mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
-                    ic.commitCorrection(correctionInfo);
-                }
+                InputConnectionCompatUtils.commitCorrection(
+                        ic,  mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
                 if (mCandidateView != null)
                     mCandidateView.onAutoCorrectionInverted(mBestWord);
             }
@@ -1418,7 +1428,7 @@
 
     private void handleClose() {
         commitTyped(getCurrentInputConnection());
-        mVoiceConnector.handleClose();
+        mVoiceProxy.handleClose();
         requestHideSelf(0);
         LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (inputView != null)
@@ -1436,7 +1446,7 @@
 
         // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
         final String resultCopy = result.toString();
-        TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
+        WordAlternatives entry = new WordAlternatives(resultCopy,
                 new WordComposer(mWord));
         mWordHistory.add(entry);
     }
@@ -1491,7 +1501,7 @@
     }
 
     public void setSuggestions(SuggestedWords words) {
-        if (mVoiceConnector.getAndResetIsShowingHint()) {
+        if (mVoiceProxy.getAndResetIsShowingHint()) {
              setCandidatesView(mCandidateViewContainer);
         }
 
@@ -1507,7 +1517,7 @@
     public void updateSuggestions() {
         // Check if we have a suggestion engine attached.
         if ((mSuggest == null || !isSuggestionsRequested())
-                && !mVoiceConnector.isVoiceInputHighlighted()) {
+                && !mVoiceProxy.isVoiceInputHighlighted()) {
             return;
         }
 
@@ -1602,7 +1612,7 @@
 
     public void pickSuggestionManually(int index, CharSequence suggestion) {
         SuggestedWords suggestions = mCandidateView.getSuggestions();
-        mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
+        mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
 
         final boolean recorrecting = TextEntryState.isRecorrecting();
         InputConnection ic = getCurrentInputConnection();
@@ -1706,7 +1716,7 @@
             return;
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
-            mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
+            mVoiceProxy.rememberReplacedWord(suggestion, mWordSeparators);
             ic.commitText(suggestion, 1);
         }
         saveWordInHistory(suggestion);
@@ -1727,9 +1737,7 @@
         // Search old suggestions to suggest re-corrected suggestions.
         for (WordAlternatives entry : mWordHistory) {
             if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
-                if (entry instanceof TypedWordAlternatives) {
-                    foundWord = ((TypedWordAlternatives) entry).word;
-                }
+                foundWord = entry.mWordComposer;
                 alternatives = entry;
                 break;
             }
@@ -1749,7 +1757,7 @@
         // Found a match, show suggestions
         if (foundWord != null || alternatives != null) {
             if (alternatives == null) {
-                alternatives = new TypedWordAlternatives(touching.mWord, foundWord);
+                alternatives = new WordAlternatives(touching.mWord, foundWord);
             }
             showCorrections(alternatives);
             if (foundWord != null) {
@@ -1763,7 +1771,7 @@
     }
 
     private void setOldSuggestions() {
-        mVoiceConnector.setShowingVoiceSuggestions(false);
+        mVoiceProxy.setShowingVoiceSuggestions(false);
         if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
             return;
         }
@@ -1777,7 +1785,7 @@
             if (touching != null && touching.mWord.length() > 1) {
                 ic.beginBatchEdit();
 
-                if (!mVoiceConnector.applyVoiceAlternatives(touching)
+                if (!mVoiceProxy.applyVoiceAlternatives(touching)
                         && !applyTypedAlternatives(touching)) {
                     abortRecorrection(true);
                 } else {
@@ -1935,8 +1943,8 @@
         }
         // Reload keyboard because the current language has been changed.
         mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
-                mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(),
-                mVoiceConnector.isVoiceButtonOnPrimary());
+                mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
+                mVoiceProxy.isVoiceButtonOnPrimary());
         initSuggest();
         mKeyboardSwitcher.updateShiftState();
     }
@@ -2108,9 +2116,8 @@
     private void loadSettings(EditorInfo attribute) {
         // Get the settings preferences
         final SharedPreferences prefs = mPrefs;
-        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
-        mVibrateOn = vibrator != null && vibrator.hasVibrator()
-                && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
+        final boolean hasVibrator = VibratorCompatWrapper.getInstance(this).hasVibrator();
+        mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
         mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
                 mResources.getBoolean(R.bool.config_default_sound_enabled));
 
@@ -2122,7 +2129,7 @@
         mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
         loadAndSetAutoCorrectionThreshold(prefs);
 
-        mVoiceConnector.loadSettings(attribute, prefs);
+        mVoiceProxy.loadSettings(attribute, prefs);
 
         updateCorrectionMode();
         updateAutoTextEnabled();
@@ -2233,13 +2240,10 @@
                 di.dismiss();
                 switch (position) {
                 case 0:
-                    Intent intent = new Intent(
-                            android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    Intent intent = CompatUtils.getInputLanguageSelectionIntent(
+                            mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                    intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID,
-                            mInputMethodId);
                     startActivity(intent);
                     break;
                 case 1:
@@ -2332,9 +2336,4 @@
         for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
         System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
     }
-
-    @Override
-    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
-        SubtypeSwitcher.getInstance().updateSubtype(subtype);
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
new file mode 100644
index 0000000..90726b0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+import java.util.Locale;
+
+class PrivateBinaryDictionaryGetter {
+    private PrivateBinaryDictionaryGetter() {}
+    public static AssetFileAddress getDictionaryFile(Locale locale, Context context) {
+        return null;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 341d5ad..488ab09 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -16,17 +16,17 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInputLogger;
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.backup.BackupManager;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
-import android.os.Vibrator;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
@@ -82,7 +82,7 @@
 
     private AlertDialog mDialog;
 
-    private VoiceInputLogger mLogger;
+    private VoiceProxy.VoiceLoggerWrapper mVoiceLogger;
 
     private boolean mOkClicked = false;
     private String mVoiceModeOff;
@@ -111,7 +111,7 @@
         mVoiceModeOff = getString(R.string.voice_mode_off);
         mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
                 .equals(mVoiceModeOff));
-        mLogger = VoiceInputLogger.getLogger(this);
+        mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(this);
 
         mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
         mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
@@ -134,8 +134,7 @@
             generalSettings.removePreference(mVoicePreference);
         }
 
-        Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
-        if (vibrator == null || !vibrator.hasVibrator()) {
+        if (!VibratorCompatWrapper.getInstance(this).hasVibrator()) {
             generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
         }
 
@@ -184,7 +183,7 @@
             ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY))
                     .removePreference(mQuickFixes);
         }
-        if (!VoiceIMEConnector.VOICE_INSTALLED
+        if (!VoiceProxy.VOICE_INSTALLED
                 || !SpeechRecognizer.isRecognitionAvailable(this)) {
             getPreferenceScreen().removePreference(mVoicePreference);
         } else {
@@ -222,16 +221,9 @@
     @Override
     public boolean onPreferenceClick(Preference pref) {
         if (pref == mInputLanguageSelection) {
-            final String action;
-            if (android.os.Build.VERSION.SDK_INT
-                    >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
-                // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
-                // TODO: Can this be a constant instead of literal String constant?
-                action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
-            } else {
-                action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
-            }
-            startActivity(new Intent(action));
+            startActivity(CompatUtils.getInputLanguageSelectionIntent(
+                    Utils.getInputMethodId(InputMethodManagerCompatWrapper.getInstance(this),
+                            getApplicationInfo().packageName), 0));
             return true;
         }
         return false;
@@ -277,10 +269,10 @@
                     public void onClick(DialogInterface dialog, int whichButton) {
                         if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
                             mVoicePreference.setValue(mVoiceModeOff);
-                            mLogger.settingsWarningDialogCancel();
+                            mVoiceLogger.settingsWarningDialogCancel();
                         } else if (whichButton == DialogInterface.BUTTON_POSITIVE) {
                             mOkClicked = true;
-                            mLogger.settingsWarningDialogOk();
+                            mVoiceLogger.settingsWarningDialogOk();
                         }
                         updateVoicePreference();
                     }
@@ -311,7 +303,7 @@
                 AlertDialog dialog = builder.create();
                 mDialog = dialog;
                 dialog.setOnDismissListener(this);
-                mLogger.settingsWarningDialogShown();
+                mVoiceLogger.settingsWarningDialogShown();
                 return dialog;
             default:
                 Log.e(TAG, "unknown dialog " + id);
@@ -321,7 +313,7 @@
 
     @Override
     public void onDismiss(DialogInterface dialog) {
-        mLogger.settingsWarningDialogDismissed();
+        mVoiceLogger.settingsWarningDialogDismissed();
         if (!mOkClicked) {
             // This assumes that onPreferenceClick gets called first, and this if the user
             // agreed after the warning, we set the mOkClicked value to true.
@@ -331,10 +323,6 @@
 
     private void updateVoicePreference() {
         boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff);
-        if (isChecked) {
-            mLogger.voiceInputSettingEnabled();
-        } else {
-            mLogger.voiceInputSettingDisabled();
-        }
+        mVoiceLogger.voiceInputSettingEnabled(isChecked);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index dc14d77..10a3369 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -16,11 +16,12 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.voice.SettingsUtil;
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInput;
 
 import android.content.Context;
 import android.content.Intent;
@@ -31,12 +32,10 @@
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
+import android.os.AsyncTask;
 import android.os.IBinder;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -59,26 +58,27 @@
     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
     private /* final */ LatinIME mService;
     private /* final */ SharedPreferences mPrefs;
-    private /* final */ InputMethodManager mImm;
+    private /* final */ InputMethodManagerCompatWrapper mImm;
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
     private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
-    private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
-            new ArrayList<InputMethodSubtype>();
+    private final ArrayList<InputMethodSubtypeCompatWrapper>
+            mEnabledKeyboardSubtypesOfCurrentInputMethod =
+                    new ArrayList<InputMethodSubtypeCompatWrapper>();
     private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
 
     /*-----------------------------------------------------------*/
     // Variants which should be changed only by reload functions.
     private boolean mNeedsToDisplayLanguage;
     private boolean mIsSystemLanguageSameAsInputLanguage;
-    private InputMethodInfo mShortcutInputMethodInfo;
-    private InputMethodSubtype mShortcutSubtype;
-    private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
-    private InputMethodSubtype mCurrentSubtype;
+    private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
+    private InputMethodSubtypeCompatWrapper mShortcutSubtype;
+    private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
+    private InputMethodSubtypeCompatWrapper mCurrentSubtype;
     private Locale mSystemLocale;
     private Locale mInputLocale;
     private String mInputLocaleStr;
-    private VoiceInput mVoiceInput;
+    private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
     /*-----------------------------------------------------------*/
 
     private boolean mIsNetworkConnected;
@@ -102,7 +102,7 @@
         mService = service;
         mPrefs = prefs;
         mResources = service.getResources();
-        mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+        mImm = InputMethodManagerCompatWrapper.getInstance(service);
         mConnectivityManager = (ConnectivityManager) service.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
@@ -113,7 +113,7 @@
         mCurrentSubtype = null;
         mAllEnabledSubtypesOfCurrentInputMethod = null;
         // TODO: Voice input should be created here
-        mVoiceInput = null;
+        mVoiceInputWrapper = null;
         mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean(
                 R.bool.config_use_spacebar_language_switcher);
         if (mConfigUseSpacebarLanguageSwitcher)
@@ -150,7 +150,7 @@
                 null, true);
         mEnabledLanguagesOfCurrentInputMethod.clear();
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
-        for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) {
+        for (InputMethodSubtypeCompatWrapper ims: mAllEnabledSubtypesOfCurrentInputMethod) {
             final String locale = ims.getLocale();
             final String mode = ims.getMode();
             mLocaleSplitter.setString(locale);
@@ -184,10 +184,10 @@
                             + ", " + mShortcutSubtype.getMode())));
         }
         // TODO: Update an icon for shortcut IME
-        Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
+        final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
                 mImm.getShortcutInputMethodsAndSubtypes();
-        for (InputMethodInfo imi: shortcuts.keySet()) {
-            List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+        for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
+            List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
             // appropriate.
             mShortcutInputMethodInfo = imi;
@@ -206,11 +206,11 @@
     }
 
     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
-    public void updateSubtype(InputMethodSubtype newSubtype) {
+    public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
         final String newLocale;
         final String newMode;
         final String oldMode = getCurrentSubtypeMode();
-        if (newSubtype == null) {
+        if (newSubtype == null || !newSubtype.hasOriginalObject()) {
             // Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
             // fallback to the default locale.
             Log.w(TAG, "Couldn't get the current subtype.");
@@ -243,30 +243,30 @@
         // We cancel its status when we change mode, while we reset otherwise.
         if (isKeyboardMode()) {
             if (modeChanged) {
-                if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
-                    mVoiceInput.cancel();
+                if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
+                    mVoiceInputWrapper.cancel();
                 }
             }
             if (modeChanged || languageChanged) {
                 updateShortcutIME();
                 mService.onRefreshKeyboard();
             }
-        } else if (isVoiceMode() && mVoiceInput != null) {
+        } else if (isVoiceMode() && mVoiceInputWrapper != null) {
             if (VOICE_MODE.equals(oldMode)) {
-                mVoiceInput.reset();
+                mVoiceInputWrapper.reset();
             }
             // If needsToShowWarningDialog is true, voice input need to show warning before
             // show recognition view.
             if (languageChanged || modeChanged
-                    || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) {
+                    || VoiceProxy.getInstance().needsToShowWarningDialog()) {
                 triggerVoiceIME();
             }
         } else {
             Log.w(TAG, "Unknown subtype mode: " + newMode);
-            if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
+            if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
                 // We need to reset the voice input to release the resources and to reset its status
                 // as it is not the current input mode.
-                mVoiceInput.reset();
+                mVoiceInputWrapper.reset();
             }
         }
     }
@@ -308,20 +308,33 @@
             return;
         }
         final String imiId = mShortcutInputMethodInfo.getId();
-        final InputMethodSubtype subtype = mShortcutSubtype;
-        new Thread("SwitchToShortcutIME") {
+        final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
+        new AsyncTask<Void, Void, Void>() {
             @Override
-            public void run() {
+            protected Void doInBackground(Void... params) {
                 mImm.setInputMethodAndSubtype(token, imiId, subtype);
+                return null;
             }
-        }.start();
+
+            @Override
+            protected void onPostExecute(Void result) {
+                // Calls in this method need to be done in the same thread as the thread which
+                // called switchToShortcutIME().
+
+                // Notify an event that the current subtype was changed. This event will be
+                // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
+                // when the API level is 10 or previous.
+                mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
+            }
+        }.execute();
     }
 
     public Drawable getShortcutIcon() {
         return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
     }
 
-    private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
+    private Drawable getSubtypeIcon(
+            InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
         final PackageManager pm = mService.getPackageManager();
         if (imi != null) {
             final String imiPackageName = imi.getPackageName();
@@ -361,8 +374,9 @@
         if (mShortcutSubtype == null)
             return true;
         final boolean allowsImplicitlySelectedSubtypes = true;
-        for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
-                mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+        for (final InputMethodSubtypeCompatWrapper enabledSubtype :
+                mImm.getEnabledInputMethodSubtypeList(
+                        mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
             if (enabledSubtype.equals(mShortcutSubtype))
                 return true;
         }
@@ -507,9 +521,9 @@
     // Voice Input functions //
     ///////////////////////////
 
-    public boolean setVoiceInput(VoiceInput vi) {
-        if (mVoiceInput == null && vi != null) {
-            mVoiceInput = vi;
+    public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
+        if (mVoiceInputWrapper == null && vi != null) {
+            mVoiceInputWrapper = vi;
             if (isVoiceMode()) {
                 if (DBG) {
                     Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
@@ -527,7 +541,7 @@
 
     private void triggerVoiceIME() {
         if (!mService.isInputViewShown()) return;
-        VoiceIMEConnector.getInstance().startListening(false,
+        VoiceProxy.getInstance().startListening(false,
                 KeyboardSwitcher.getInstance().getInputView().getWindowToken());
     }
 
@@ -612,30 +626,14 @@
     }
 
 
-    // A list of locales which are supported by default for voice input, unless we get a
-    // different list from Gservices.
-    private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
-            "en " +
-            "en_US " +
-            "en_GB " +
-            "en_AU " +
-            "en_CA " +
-            "en_IE " +
-            "en_IN " +
-            "en_NZ " +
-            "en_SG " +
-            "en_ZA ";
-
     public boolean isVoiceSupported(String locale) {
         // Get the current list of supported locales and check the current locale against that
         // list. We cache this value so as not to check it every time the user starts a voice
         // input. Because this method is called by onStartInputView, this should mean that as
         // long as the locale doesn't change while the user is keeping the IME open, the
         // value should never be stale.
-        String supportedLocalesString = SettingsUtil.getSettingsString(
-                mService.getContentResolver(),
-                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
-                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+        String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
+                mService.getContentResolver());
         List<String> voiceInputSupportedLocales = Arrays.asList(
                 supportedLocalesString.split("\\s+"));
         return voiceInputSupportedLocales.contains(locale);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0de474e..0cc9d41 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -27,6 +27,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -47,7 +48,7 @@
 
     /**
      * Words that appear in both bigram and unigram data gets multiplier ranging from
-     * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from
+     * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from
      * bigram data.
      */
     public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
@@ -92,13 +93,13 @@
     private boolean mQuickFixesEnabled;
 
     private double mAutoCorrectionThreshold;
-    private int[] mPriorities = new int[mPrefMaxSuggestions];
-    private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+    private int[] mScores = new int[mPrefMaxSuggestions];
+    private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
 
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
     ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
     private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
-    private String mLowerOriginalWord;
+    private CharSequence mTypedWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
     private boolean mIsFirstCharCapitalized;
@@ -106,12 +107,15 @@
 
     private int mCorrectionMode = CORRECTION_BASIC;
 
-    public Suggest(Context context, int dictionaryResId) {
-        init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN));
+    public Suggest(Context context, int dictionaryResId, Locale locale) {
+        init(context, BinaryDictionary.initDictionaryFromManager(context, DIC_MAIN, locale,
+                dictionaryResId));
     }
 
-    /* package for test */ Suggest(File dictionary, long startOffset, long length) {
-        init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN));
+    /* package for test */ Suggest(File dictionary, long startOffset, long length,
+            BinaryDictionary.Flag[] flagArray) {
+        init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN,
+                flagArray));
     }
 
     private void init(Context context, BinaryDictionary mainDict) {
@@ -128,6 +132,19 @@
         initPool();
     }
 
+    public void resetMainDict(Context context, int dictionaryResId, Locale locale) {
+        final BinaryDictionary newMainDict = BinaryDictionary.initDictionaryFromManager(context,
+                DIC_MAIN, locale, dictionaryResId);
+        mMainDict = newMainDict;
+        if (null == newMainDict) {
+            mUnigramDictionaries.remove(DICT_KEY_MAIN);
+            mBigramDictionaries.remove(DICT_KEY_MAIN);
+        } else {
+            mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+            mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+        }
+    }
+
     private void initPool() {
         for (int i = 0; i < mPrefMaxSuggestions; i++) {
             StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -207,8 +224,8 @@
             throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
         }
         mPrefMaxSuggestions = maxSuggestions;
-        mPriorities = new int[mPrefMaxSuggestions];
-        mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+        mScores = new int[mPrefMaxSuggestions];
+        mBigramScores = new int[PREF_MAX_BIGRAMS];
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
         while (mStringPool.size() < mPrefMaxSuggestions) {
             StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -256,25 +273,23 @@
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
-        Arrays.fill(mPriorities, 0);
+        Arrays.fill(mScores, 0);
 
         // Save a lowercase version of the original word
         CharSequence typedWord = wordComposer.getTypedWord();
         if (typedWord != null) {
             final String typedWordString = typedWord.toString();
             typedWord = typedWordString;
-            mLowerOriginalWord = typedWordString.toLowerCase();
             // Treating USER_TYPED as UNIGRAM suggestion for logging now.
             LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED,
                     Dictionary.DataType.UNIGRAM);
-        } else {
-            mLowerOriginalWord = "";
         }
+        mTypedWord = typedWord;
 
         if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
                 || mCorrectionMode == CORRECTION_BASIC)) {
             // At first character typed, search only the bigrams
-            Arrays.fill(mBigramPriorities, 0);
+            Arrays.fill(mBigramScores, 0);
             collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
 
             if (!TextUtils.isEmpty(prevWordForBigram)) {
@@ -346,7 +361,7 @@
                 mWhiteListDictionary.getWhiteListedWord(typedWordString));
 
         mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
-                mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+                mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
                 autoText, whitelistedWord);
 
         if (autoText != null) {
@@ -364,26 +379,25 @@
 
         if (DBG) {
             double normalizedScore = mAutoCorrection.getNormalizedScore();
-            ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+            ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
                     new ArrayList<SuggestedWords.SuggestedWordInfo>();
-            frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
-            final int priorityLength = mPriorities.length;
-            for (int i = 0; i < priorityLength; ++i) {
+            scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+            for (int i = 0; i < mScores.length; ++i) {
                 if (normalizedScore > 0) {
-                    final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
-                            normalizedScore + ")";
-                    frequencyInfoList.add(
-                            new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+                    final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
+                            normalizedScore);
+                    scoreInfoList.add(
+                            new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
                     normalizedScore = 0.0;
                 } else {
-                    final String priority = Integer.toString(mPriorities[i]);
-                    frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+                    final String score = Integer.toString(mScores[i]);
+                    scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
                 }
             }
-            for (int i = priorityLength; i < mSuggestions.size(); ++i) {
-                frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+            for (int i = mScores.length; i < mSuggestions.size(); ++i) {
+                scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
             }
-            return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+            return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
         }
         return new SuggestedWords.Builder().addWords(mSuggestions, null);
     }
@@ -419,52 +433,37 @@
         return mAutoCorrection.hasAutoCorrection();
     }
 
-    private static boolean compareCaseInsensitive(final String lowerOriginalWord,
-            final char[] word, final int offset, final int length) {
-        final int originalLength = lowerOriginalWord.length();
-        if (originalLength == length && Character.isUpperCase(word[offset])) {
-            for (int i = 0; i < originalLength; i++) {
-                if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        return false;
-    }
-
     @Override
-    public boolean addWord(final char[] word, final int offset, final int length, int freq,
+    public boolean addWord(final char[] word, final int offset, final int length, int score,
             final int dicTypeId, final Dictionary.DataType dataType) {
         Dictionary.DataType dataTypeForLog = dataType;
-        ArrayList<CharSequence> suggestions;
-        int[] priorities;
-        int prefMaxSuggestions;
+        final ArrayList<CharSequence> suggestions;
+        final int[] sortedScores;
+        final int prefMaxSuggestions;
         if(dataType == Dictionary.DataType.BIGRAM) {
             suggestions = mBigramSuggestions;
-            priorities = mBigramPriorities;
+            sortedScores = mBigramScores;
             prefMaxSuggestions = PREF_MAX_BIGRAMS;
         } else {
             suggestions = mSuggestions;
-            priorities = mPriorities;
+            sortedScores = mScores;
             prefMaxSuggestions = mPrefMaxSuggestions;
         }
 
         int pos = 0;
 
         // Check if it's the same word, only caps are different
-        if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
+        if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
             // TODO: remove this surrounding if clause and move this logic to
             // getSuggestedWordBuilder.
             if (suggestions.size() > 0) {
-                final String currentHighestWordLowerCase =
-                        suggestions.get(0).toString().toLowerCase();
+                final String currentHighestWord = suggestions.get(0).toString();
                 // If the current highest word is also equal to typed word, we need to compare
                 // frequency to determine the insertion position. This does not ensure strictly
                 // correct ordering, but ensures the top score is on top which is enough for
                 // removing duplicates correctly.
-                if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length)
-                        && freq <= priorities[0]) {
+                if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
+                        && score <= sortedScores[0]) {
                     pos = 1;
                 }
             }
@@ -475,24 +474,24 @@
                 if(bigramSuggestion >= 0) {
                     dataTypeForLog = Dictionary.DataType.BIGRAM;
                     // turn freq from bigram into multiplier specified above
-                    double multiplier = (((double) mBigramPriorities[bigramSuggestion])
+                    double multiplier = (((double) mBigramScores[bigramSuggestion])
                             / MAXIMUM_BIGRAM_FREQUENCY)
                             * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
                             + BIGRAM_MULTIPLIER_MIN;
                     /* Log.d(TAG,"bigram num: " + bigramSuggestion
                             + "  wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
-                            + "  currentPriority: " + freq + "  bigramPriority: "
-                            + mBigramPriorities[bigramSuggestion]
+                            + "  currentScore: " + score + "  bigramScore: "
+                            + mBigramScores[bigramSuggestion]
                             + "  multiplier: " + multiplier); */
-                    freq = (int)Math.round((freq * multiplier));
+                    score = (int)Math.round((score * multiplier));
                 }
             }
 
-            // Check the last one's priority and bail
-            if (priorities[prefMaxSuggestions - 1] >= freq) return true;
+            // Check the last one's score and bail
+            if (sortedScores[prefMaxSuggestions - 1] >= score) return true;
             while (pos < prefMaxSuggestions) {
-                if (priorities[pos] < freq
-                        || (priorities[pos] == freq && length < suggestions.get(pos).length())) {
+                if (sortedScores[pos] < score
+                        || (sortedScores[pos] == score && length < suggestions.get(pos).length())) {
                     break;
                 }
                 pos++;
@@ -502,8 +501,8 @@
             return true;
         }
 
-        System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
-        priorities[pos] = freq;
+        System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
+        sortedScores[pos] = score;
         int poolSize = mStringPool.size();
         StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
                 : new StringBuilder(getApproxMaxWordLength());
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 727e3f1..35b2b12 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardId;
 
 import android.content.res.Resources;
@@ -28,8 +31,6 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -101,15 +102,15 @@
         }
     }
 
-    public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) {
+    public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) {
         return imm.getEnabledInputMethodList().size() > 1
         // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
         // input method subtype (The current IME should be LatinIME.)
                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
     }
 
-    public static String getInputMethodId(InputMethodManager imm, String packageName) {
-        for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+    public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
+        for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
             if (imi.getPackageName().equals(packageName))
                 return imi.getId();
         }
@@ -285,7 +286,7 @@
 
     // In dictionary.cpp, getSuggestion() method,
     // suggestion scores are computed using the below formula.
-    // original score (called 'frequency')
+    // original score
     //  := pow(mTypedLetterMultiplier (this is defined 2),
     //         (the number of matched characters between typed word and suggested word))
     //     * (individual word's score which defined in the unigram dictionary,
@@ -295,7 +296,7 @@
     //       (full match up to min(before.length(), after.length())
     //       => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
     //     - If the word is a true full match except for differences in accents or
-    //       capitalization, then treat it as if the frequency was 255.
+    //       capitalization, then treat it as if the score was 255.
     //     - If before.length() == after.length()
     //       => multiply by mFullWordMultiplier (this is defined 2))
     // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
@@ -485,7 +486,7 @@
         case InputType.TYPE_CLASS_PHONE:
             return KeyboardId.MODE_PHONE;
         case InputType.TYPE_CLASS_TEXT:
-            if (Utils.isEmailVariation(variation)) {
+            if (InputTypeCompatUtils.isEmailVariation(variation)) {
                 return KeyboardId.MODE_EMAIL;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
                 return KeyboardId.MODE_URL;
@@ -503,31 +504,6 @@
         }
     }
 
-    public static boolean isEmailVariation(int variation) {
-        return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
-                || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
-    }
-
-    // Please refer to TextView.isPasswordInputType
-    public static boolean isPasswordInputType(int inputType) {
-        final int variation =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return (variation
-                == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD))
-                || (variation
-                == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
-                || (variation
-                == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
-    }
-
-    // Please refer to TextView.isVisiblePasswordInputType
-    public static boolean isVisiblePasswordInputType(int inputType) {
-        final int variation =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return variation
-                == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
-    }
-
     public static boolean containsInCsv(String key, String csv) {
         if (csv == null)
             return false;
@@ -551,7 +527,9 @@
      * @return main dictionary resource id
      */
     public static int getMainDictionaryResourceId(Resources res) {
-        return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName());
+        final String MAIN_DIC_NAME = "main";
+        String packageName = LatinIME.class.getPackage().getName();
+        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
     }
 
     public static void loadNativeLibrary() {
@@ -561,4 +539,66 @@
             Log.e(TAG, "Could not load native library jni_latinime");
         }
     }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the character.
+     * @param a first character to check
+     * @param b second character to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(char a, char b) {
+        // Some language, such as Turkish, need testing both cases.
+        return a == b
+                || Character.toLowerCase(a) == Character.toLowerCase(b)
+                || Character.toUpperCase(a) == Character.toUpperCase(b);
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if they are
+     * both null.
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+        if (a == b)
+            return true;  // including both a and b are null.
+        if (a == null || b == null)
+            return false;
+        final int length = a.length();
+        if (length != b.length())
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+     * and b is zero length.
+     * @param a CharSequence to check
+     * @param b character array to check
+     * @param offset start offset of array b
+     * @param length length of characters in array b
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     * @throws IndexOutOfBoundsException
+     *   if {@code offset < 0 || length < 0 || offset + length > data.length}.
+     * @throws NullPointerException if {@code b == null}.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+        if (offset < 0 || length < 0 || length > b.length - offset)
+            throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+                    + " length=" + length);
+        if (a == null)
+            return length == 0;  // including a is null and b is zero length.
+        if (a.length() != length)
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+                return false;
+        }
+        return true;
+    }
 }
diff --git a/native/Android.mk b/native/Android.mk
index c8342e3..4727b1e 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -3,6 +3,11 @@
 
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
 
+LOCAL_CFLAGS += -Werror -Wall
+
+# To suppress compiler warnings for unused variables/functions used for debug features etc.
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
+
 LOCAL_SRC_FILES := \
     jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \
     jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
diff --git a/native/src/bigram_dictionary.cpp b/native/src/bigram_dictionary.cpp
index 5ec310f..36761b8 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/src/bigram_dictionary.cpp
@@ -30,8 +30,10 @@
     : DICT(dict), MAX_WORD_LENGTH(maxWordLength),
     MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion),
     HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) {
-    if (DEBUG_DICT) LOGI("BigramDictionary - constructor");
-    if (DEBUG_DICT) LOGI("Has Bigram : %d", hasBigram);
+    if (DEBUG_DICT) {
+        LOGI("BigramDictionary - constructor");
+        LOGI("Has Bigram : %d", hasBigram);
+    }
 }
 
 BigramDictionary::~BigramDictionary() {
@@ -54,7 +56,9 @@
         }
         insertAt++;
     }
-    if (DEBUG_DICT) LOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+    if (DEBUG_DICT) {
+        LOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+    }
     if (insertAt < mMaxBigrams) {
         memmove((char*) mBigramFreq + (insertAt + 1) * sizeof(mBigramFreq[0]),
                (char*) mBigramFreq + insertAt * sizeof(mBigramFreq[0]),
@@ -68,7 +72,9 @@
             *dest++ = *word++;
         }
         *dest = 0; // NULL terminate
-        if (DEBUG_DICT) LOGI("Bigram: Added word at %d", insertAt);
+        if (DEBUG_DICT) {
+            LOGI("Bigram: Added word at %d", insertAt);
+        }
         return true;
     }
     return false;
@@ -107,7 +113,9 @@
     if (HAS_BIGRAM && IS_LATEST_DICT_VERSION) {
         int pos = mParentDictionary->isValidWordRec(
                 DICTIONARY_HEADER_SIZE, prevWord, 0, prevWordLength);
-        if (DEBUG_DICT) LOGI("Pos -> %d", pos);
+        if (DEBUG_DICT) {
+            LOGI("Pos -> %d", pos);
+        }
         if (pos < 0) {
             return 0;
         }
@@ -151,7 +159,9 @@
         }
         pos = followDownBranchAddress; // pos start at count
         int count = DICT[pos] & 0xFF;
-        if (DEBUG_DICT) LOGI("count - %d",count);
+        if (DEBUG_DICT) {
+            LOGI("count - %d",count);
+        }
         pos++;
         for (int i = 0; i < count; i++) {
             // pos at data
@@ -225,7 +235,9 @@
         }
         depth++;
         if (followDownBranchAddress == 0) {
-            if (DEBUG_DICT) LOGI("ERROR!!! Cannot find bigram!!");
+            if (DEBUG_DICT) {
+                LOGI("ERROR!!! Cannot find bigram!!");
+            }
             break;
         }
     }
diff --git a/native/src/defines.h b/native/src/defines.h
index 00cbb6c..9261207 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -77,8 +77,8 @@
 }
 
 #else // FLAG_DBG
-#define LOGE
-#define LOGI
+#define LOGE(fmt, ...)
+#define LOGI(fmt, ...)
 #define DEBUG_DICT false
 #define DEBUG_DICT_FULL false
 #define DEBUG_SHOW_FOUND_WORD false
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 0f12018..c2062e8 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -32,13 +32,13 @@
     bool hasSpaceProximity(const int x, const int y) const;
 private:
     int getStartIndexFromCoordinates(const int x, const int y) const;
-    const int CELL_WIDTH;
-    const int CELL_HEIGHT;
+    const int MAX_PROXIMITY_CHARS_SIZE;
     const int KEYBOARD_WIDTH;
     const int KEYBOARD_HEIGHT;
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
-    const int MAX_PROXIMITY_CHARS_SIZE;
+    const int CELL_WIDTH;
+    const int CELL_HEIGHT;
     uint32_t *mProximityCharsArray;
 };
 }; // namespace latinime
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 30fbaea..c188290 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -43,7 +43,9 @@
     ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0),
     BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)),
     MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
-    if (DEBUG_DICT) LOGI("UnigramDictionary - constructor");
+    if (DEBUG_DICT) {
+        LOGI("UnigramDictionary - constructor");
+    }
 }
 
 UnigramDictionary::~UnigramDictionary() {}
@@ -183,7 +185,9 @@
     // Suggestion with missing character
     if (SUGGEST_WORDS_WITH_MISSING_CHARACTER) {
         for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest missing characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest missing characters %d", i);
+            }
             getSuggestionCandidates(i, -1, -1, NULL, 0, MAX_DEPTH);
         }
     }
@@ -194,7 +198,9 @@
     if (SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER
             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION) {
         for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest excessive characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest excessive characters %d", i);
+            }
             getSuggestionCandidates(-1, i, -1, NULL, 0, MAX_DEPTH);
         }
     }
@@ -205,7 +211,9 @@
     // Only suggest words that length is mInputLength
     if (SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS) {
         for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest transposed characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest transposed characters %d", i);
+            }
             getSuggestionCandidates(-1, -1, i, NULL, 0, mInputLength - 1);
         }
     }
@@ -216,7 +224,9 @@
     if (SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER
             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
         for (int i = 1; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest missing space characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest missing space characters %d", i);
+            }
             getMissingSpaceWords(mInputLength, i);
         }
     }
@@ -226,12 +236,15 @@
     if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY) {
         // The first and last "mistyped spaces" are taken care of by excessive character handling
         for (int i = 1; i < codesSize - 1; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest words with proximity space %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest words with proximity space %d", i);
+            }
             const int x = xcoordinates[i];
             const int y = ycoordinates[i];
-            if (DEBUG_PROXIMITY_INFO)
+            if (DEBUG_PROXIMITY_INFO) {
                 LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
                         i, x, y, proximityInfo->hasSpaceProximity(x, y));
+            }
             if (proximityInfo->hasSpaceProximity(x, y)) {
                 getMistypedSpaceWords(mInputLength, i);
             }
@@ -242,7 +255,9 @@
 
 void UnigramDictionary::initSuggestions(const int *codes, const int codesSize,
         unsigned short *outWords, int *frequencies) {
-    if (DEBUG_DICT) LOGI("initSuggest");
+    if (DEBUG_DICT) {
+        LOGI("initSuggest");
+    }
     mFrequencies = frequencies;
     mOutputChars = outWords;
     mInputCodes = codes;
@@ -266,7 +281,9 @@
         LOGI("Found word = %s, freq = %d", s, frequency);
     }
     if (length > MAX_WORD_LENGTH) {
-        if (DEBUG_DICT) LOGI("Exceeded max word length.");
+        if (DEBUG_DICT) {
+            LOGI("Exceeded max word length.");
+        }
         return false;
     }
 
@@ -297,7 +314,9 @@
             *dest++ = *word++;
         }
         *dest = 0; // NULL terminate
-        if (DEBUG_DICT) LOGI("Added word at %d", insertAt);
+        if (DEBUG_DICT) {
+            LOGI("Added word at %d", insertAt);
+        }
         return true;
     }
     return false;
@@ -409,7 +428,9 @@
     // Allocating variable length array on stack
     unsigned short word[newWordLength];
     const int firstFreq = getBestWordFreq(firstWordStartPos, firstWordLength, mWord);
-    if (DEBUG_DICT) LOGI("First freq: %d", firstFreq);
+    if (DEBUG_DICT) {
+        LOGI("First freq: %d", firstFreq);
+    }
     if (firstFreq <= 0) return false;
 
     for (int i = 0; i < firstWordLength; ++i) {
@@ -417,7 +438,9 @@
     }
 
     const int secondFreq = getBestWordFreq(secondWordStartPos, secondWordLength, mWord);
-    if (DEBUG_DICT) LOGI("Second  freq:  %d", secondFreq);
+    if (DEBUG_DICT) {
+        LOGI("Second  freq:  %d", secondFreq);
+    }
     if (secondFreq <= 0) return false;
 
     word[firstWordLength] = SPACE;
@@ -514,7 +537,9 @@
     for (int i = 0; i < depth; ++i) lengthFreq *= TYPED_LETTER_MULTIPLIER;
     if (lengthFreq == matchWeight) {
         if (depth > 1) {
-            if (DEBUG_DICT) LOGI("Found full matched word.");
+            if (DEBUG_DICT) {
+                LOGI("Found full matched word.");
+            }
             multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
         }
         if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
@@ -768,7 +793,9 @@
     *siblingPos = Dictionary::setDictionaryValues(DICT, IS_LATEST_DICT_VERSION, firstChildPos, &c,
             newChildPosition, newTerminal, newFreq);
     const unsigned int inputC = currentChars[0];
-    if (DEBUG_DICT) assert(inputC <= U_SHORT_MAX);
+    if (DEBUG_DICT) {
+        assert(inputC <= U_SHORT_MAX);
+    }
     const unsigned short baseLowerC = toBaseLowerCase(c);
     const bool matched = (inputC == baseLowerC || inputC == c);
     const bool hasChild = *newChildPosition != 0;
@@ -776,7 +803,9 @@
         word[depth] = c;
         if (DEBUG_DICT && DEBUG_NODE) {
             LOGI("Node(%c, %c)<%d>, %d, %d", inputC, c, matched, hasChild, *newFreq);
-            if (*newTerminal) LOGI("Terminal %d", *newFreq);
+            if (*newTerminal) {
+                LOGI("Terminal %d", *newFreq);
+            }
         }
         if (hasChild) {
             *newCount = Dictionary::getCount(DICT, newChildPosition);
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 5930ea3..1d0a5b7 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -34,7 +34,9 @@
     private final KeyDetector mKeyDetector;
 
     public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
-        mSuggest = new Suggest(context, dictionaryId);
+        // Use null as the locale for Suggest so as to force it to use the internal dictionary
+        // (and not try to find a dictionary provider for a specified locale)
+        mSuggest = new Suggest(context, dictionaryId, null);
         mKeyboard = new LatinKeyboard(context, keyboardId);
         mKeyDetector = new ProximityKeyDetector();
         init();
@@ -42,7 +44,7 @@
 
     protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length,
             KeyboardId keyboardId) {
-        mSuggest = new Suggest(dictionaryPath, startOffset, length);
+        mSuggest = new Suggest(dictionaryPath, startOffset, length, null);
         mKeyboard = new LatinKeyboard(context, keyboardId);
         mKeyDetector = new ProximityKeyDetector();
         init();