Merge "Make AutoCorrection static and non-instantiable (A5)"
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index c4596f0..f3b9347 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавиатура на Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Настройки на клавиатурата на Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Програма за правописна проверка за Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Програма за правописна проверка за Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Настройки за проверка на правописа"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Търсене на имена"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"За проверка на правописа се ползват записи от списъка с контакти"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index dffb8da..53d0252 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -37,12 +37,9 @@
     <string name="misc_category" msgid="6894192814868233453">"Other Options"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Advanced settings"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"Options for experts"</string>
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for suppress_language_switch_key (8003788410354806368) -->
-    <skip />
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Switch to other input methods"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Language switch key covers other input methods too"</string>
+    <string name="suppress_language_switch_key" msgid="8003788410354806368">"Suppress language switch key"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Key pop-up dismiss delay"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"No delay"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 8d5d26c..052c4c8 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Androidキーボード(AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Androidキーボードの設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidスペルチェッカー"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidスペルチェッカー(AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"スペルチェックの設定"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"連絡先名の検索"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"スペルチェッカーでは連絡先リストのエントリを使用します"</string>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 54fe0f7..533b10a 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Papan kekunci Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Tetapan papan kekunci Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Penyemak ejaan Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Penyemak ejaan Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Tetapan penyemakan ejaan"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Penyemak ejaan menggunakan entri dari senarai kenalan anda"</string>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index e788be6..2445075 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tastatură Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Setările tastaturii Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opţiuni de introducere text"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Verificator ortografic Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Verificator ortografic Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Setări de verificare ortografică"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Verificare nume în agendă"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Verificatorul ortografic utilizează intrări din lista de contacte"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 860c97a..3382d0e 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Kicharazio cha Android (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Mipangilio ya kibodi ya Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Chaguo za uingizaji"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kikagua tahajia ya Android"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kikagua tahajia ya Android (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Mipangilio ya kukagua sarufi"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Angalia majina ya wasiliani"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kikagua tahajia hutumia ingizo kutoka kwa orodha yako ya anwani"</string>
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 689b209..c507bd2 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -31,7 +31,7 @@
     <!-- Long pressing space will invoke IME switcher if > 0, never invoke IME switcher if == 0 -->
     <integer name="config_long_press_space_key_timeout">0</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_id" translatable="false">5</string>
+    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
     <integer name="config_max_more_keys_column">5</integer>
     <!--
         Configuration for LatinKeyboardView
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index d46e5f1..b78a6c6 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -29,7 +29,7 @@
     <!-- Long pressing space will invoke IME switcher if > 0, never invoke IME switcher if == 0 -->
     <integer name="config_long_press_space_key_timeout">0</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_id" translatable="false">5</string>
+    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
     <integer name="config_max_more_keys_column">5</integer>
     <!--
         Configuration for LatinKeyboardView
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index c66f6ec..f291fcc 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"การตั้งค่าแป้นพิมพ์ Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"แอนดรอยด์ตรวจสอบการสะกด"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"แอนดรอยด์ตรวจสอบการสะกด (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"การตั้งค่าการตรวจสอบการสะกด"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ค้นหารายชื่อติดต่อ"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"เครื่องมือตรวจการสะกดใช้รายการจากรายชื่อติดต่อของคุณ"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 3d66ec1..ef42b7f 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 键盘 (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 键盘设置"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 拼写检查工具"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 拼写检查工具 (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"拼写检查设置"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找联系人姓名"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼写检查工具会使用您的联系人列表中的条目"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 5602aaf..a2c2f02 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -24,10 +24,8 @@
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 鍵盤 (AOSP)"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 鍵盤設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
+    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 拼字檢查"</string>
+    <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 拼字檢查 (AOSP)"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"拼字檢查設定"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查詢聯絡人姓名"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人清單項目"</string>
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index f38b85f..b926ed0 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -32,7 +32,7 @@
             android:persistent="true"
             android:entryValues="@array/keyboard_layout_modes_values"
             android:entries="@array/keyboard_layout_modes"
-            android:defaultValue="@string/config_default_keyboard_theme_id"
+            android:defaultValue="@string/config_default_keyboard_theme_index"
             />
 
     <CheckBoxPreference
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 9dd0a59..bf5f201 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -32,7 +32,7 @@
 import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.SubtypeUtils;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -163,7 +163,7 @@
     private InputMethodInfoCompatWrapper getLatinImeInputMethodInfo() {
         if (TextUtils.isEmpty(mLatinImePackageName))
             return null;
-        return Utils.getInputMethodInfo(mLatinImePackageName);
+        return SubtypeUtils.getInputMethodInfo(mLatinImePackageName);
     }
 
     private static InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
@@ -260,7 +260,8 @@
 
         // The code below are based on {@link InputMethodManager#showInputMethodMenuInternal}.
 
-        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(mLatinImePackageName);
+        final InputMethodInfoCompatWrapper myImi = SubtypeUtils.getInputMethodInfo(
+                mLatinImePackageName);
         final List<InputMethodSubtypeCompatWrapper> myImsList = getEnabledInputMethodSubtypeList(
                 myImi, true);
         final InputMethodSubtypeCompatWrapper currentIms = getCurrentInputMethodSubtype();
diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index c1c6d31..87d1c11 100644
--- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -61,8 +61,8 @@
 import com.android.inputmethod.latin.LatinIME.UIHandler;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Utils;
 
 import java.util.ArrayList;
@@ -662,9 +662,9 @@
 
     private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo editorInfo) {
         @SuppressWarnings("deprecation")
-        final boolean noMic = Utils.inPrivateImeOptions(null,
+        final boolean noMic = StringUtils.inPrivateImeOptions(null,
                 LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)
-                || Utils.inPrivateImeOptions(mService.getPackageName(),
+                || StringUtils.inPrivateImeOptions(mService.getPackageName(),
                         LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo);
         return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
                 && SpeechRecognizer.isRecognitionAvailable(mService);
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
index e75e148..421ee65 100644
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
@@ -16,16 +16,6 @@
 
 package com.android.inputmethod.deprecated.languageswitcher;
 
-import com.android.inputmethod.compat.SharedPreferencesCompat;
-import com.android.inputmethod.keyboard.KeyboardSet;
-import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.Utils;
-
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.content.res.Resources;
@@ -37,6 +27,16 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
+import com.android.inputmethod.compat.SharedPreferencesCompat;
+import com.android.inputmethod.keyboard.KeyboardSet;
+import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.LocaleUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.StringUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.text.Collator;
 import java.util.ArrayList;
@@ -237,12 +237,12 @@
 
             if (finalSize == 0) {
                 preprocess[finalSize++] =
-                        new LocaleEntry(Utils.getFullDisplayName(l, false), l);
+                        new LocaleEntry(StringUtils.getFullDisplayName(l, false), l);
             } else {
                 if (s.equals("zz_ZZ")) {
                     // ignore this locale
                 } else {
-                    final String displayName = Utils.getFullDisplayName(l, false);
+                    final String displayName = StringUtils.getFullDisplayName(l, false);
                     preprocess[finalSize++] = new LocaleEntry(displayName, l);
                 }
             }
diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
index 71d15dc..ff8b1ab 100644
--- a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
@@ -16,10 +16,6 @@
 
 package com.android.inputmethod.deprecated.voice;
 
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -39,6 +35,10 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
 import java.io.ByteArrayOutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -222,7 +222,7 @@
                 Locale locale = SubtypeSwitcher.getInstance().getInputLocale();
 
                 mLanguage.setVisibility(View.VISIBLE);
-                mLanguage.setText(Utils.getFullDisplayName(locale, true));
+                mLanguage.setText(StringUtils.getFullDisplayName(locale, true));
 
                 mPopupLayout.setBackgroundDrawable(mListeningBorder);
                 break;
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f392383..6b4de18 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -30,7 +30,7 @@
 import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -293,7 +293,7 @@
         // Choose the first letter of the label as primary code if not specified.
         if (code == Keyboard.CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
                 && !TextUtils.isEmpty(mLabel)) {
-            if (Utils.codePointCount(mLabel) == 1) {
+            if (StringUtils.codePointCount(mLabel) == 1) {
                 // Use the first letter of the hint label if shiftedLetterActivated flag is
                 // specified.
                 if (hasShiftedLetterHint() && isShiftedLetterActivated()
@@ -309,7 +309,7 @@
                 mCode = Keyboard.CODE_OUTPUT_TEXT;
             }
         } else if (code == Keyboard.CODE_UNSPECIFIED && outputText != null) {
-            if (Utils.codePointCount(outputText) == 1) {
+            if (StringUtils.codePointCount(outputText) == 1) {
                 mCode = outputText.codePointAt(0);
                 outputText = null;
             } else {
@@ -336,7 +336,7 @@
         if (!Keyboard.isLetterCode(code) || preserveCase) return code;
         final String text = new String(new int[] { code } , 0, 1);
         final String casedText = adjustCaseOfStringForKeyboardId(text, preserveCase, id);
-        return Utils.codePointCount(casedText) == 1
+        return StringUtils.codePointCount(casedText) == 1
                 ? casedText.codePointAt(0) : Keyboard.CODE_UNSPECIFIED;
     }
 
@@ -484,7 +484,7 @@
     }
 
     public int selectTextSize(int letter, int largeLetter, int label, int hintLabel) {
-        if (Utils.codePointCount(mLabel) > 1
+        if (StringUtils.codePointCount(mLabel) > 1
                 && (mLabelFlags & (LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
                         | LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
             return label;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
index 731aaf7..5ac6d03 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -21,16 +21,18 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.text.InputType;
 import android.util.Log;
 import android.util.Xml;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -215,9 +217,9 @@
             mEditorInfo = editorInfo;
             final Params params = mParams;
 
-            params.mMode = Utils.getKeyboardMode(editorInfo);
+            params.mMode = getKeyboardMode(editorInfo);
             params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO;
-            params.mNoSettingsKey = Utils.inPrivateImeOptions(
+            params.mNoSettingsKey = StringUtils.inPrivateImeOptions(
                     mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo);
         }
 
@@ -230,7 +232,7 @@
         // TODO: Use InputMethodSubtype object as argument.
         public Builder setSubtype(Locale inputLocale, boolean asciiCapable,
                 boolean touchPositionCorrectionEnabled) {
-            final boolean deprecatedForceAscii = Utils.inPrivateImeOptions(
+            final boolean deprecatedForceAscii = StringUtils.inPrivateImeOptions(
                     mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo);
             final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
                     mParams.mEditorInfo.imeOptions)
@@ -243,9 +245,9 @@
         public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
                 boolean languageSwitchKeyEnabled) {
             @SuppressWarnings("deprecation")
-            final boolean deprecatedNoMicrophone = Utils.inPrivateImeOptions(
+            final boolean deprecatedNoMicrophone = StringUtils.inPrivateImeOptions(
                     null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo);
-            final boolean noMicrophone = Utils.inPrivateImeOptions(
+            final boolean noMicrophone = StringUtils.inPrivateImeOptions(
                     mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo)
                     || deprecatedNoMicrophone;
             mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
@@ -337,6 +339,44 @@
                 a.recycle();
             }
         }
+
+        private static int getKeyboardMode(EditorInfo editorInfo) {
+            if (editorInfo == null)
+                return KeyboardId.MODE_TEXT;
+
+            final int inputType = editorInfo.inputType;
+            final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+
+            switch (inputType & InputType.TYPE_MASK_CLASS) {
+            case InputType.TYPE_CLASS_NUMBER:
+                return KeyboardId.MODE_NUMBER;
+            case InputType.TYPE_CLASS_DATETIME:
+                switch (variation) {
+                case InputType.TYPE_DATETIME_VARIATION_DATE:
+                    return KeyboardId.MODE_DATE;
+                case InputType.TYPE_DATETIME_VARIATION_TIME:
+                    return KeyboardId.MODE_TIME;
+                default: // InputType.TYPE_DATETIME_VARIATION_NORMAL
+                    return KeyboardId.MODE_DATETIME;
+                }
+            case InputType.TYPE_CLASS_PHONE:
+                return KeyboardId.MODE_PHONE;
+            case InputType.TYPE_CLASS_TEXT:
+                if (InputTypeCompatUtils.isEmailVariation(variation)) {
+                    return KeyboardId.MODE_EMAIL;
+                } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
+                    return KeyboardId.MODE_URL;
+                } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+                    return KeyboardId.MODE_IM;
+                } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+                    return KeyboardId.MODE_TEXT;
+                } else {
+                    return KeyboardId.MODE_TEXT;
+                }
+            default:
+                return KeyboardId.MODE_TEXT;
+            }
+        }
     }
 
     public static String parseKeyboardLocale(Resources res, int resId)
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 78e0ee2..decd73d 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -41,7 +41,7 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.StringUtils;
 
 import java.util.HashMap;
 
@@ -853,7 +853,7 @@
         if (key.mLabel != null) {
             // TODO Should take care of temporaryShiftLabel here.
             previewText.setCompoundDrawables(null, null, null, null);
-            if (Utils.codePointCount(key.mLabel) > 1) {
+            if (StringUtils.codePointCount(key.mLabel) > 1) {
                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
                 previewText.setTypeface(Typeface.DEFAULT_BOLD);
             } else {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index afc4932..0a03075 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -46,6 +46,8 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeUtils;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 
@@ -779,12 +781,13 @@
 
             // Whether space key needs to show the "..." popup hint for special purposes
             if (mIsSpacebarTriggeringPopupByLongPress
-                    && Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+                    && SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(
+                            true /* include aux subtypes */)) {
                 drawKeyPopupHint(key, canvas, paint, params);
             }
         } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) {
             super.onDrawKeyTopVisuals(key, canvas, paint, params);
-            if (Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+            if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 drawKeyPopupHint(key, canvas, paint, params);
             }
         } else {
@@ -810,7 +813,7 @@
         paint.setTextAlign(Align.CENTER);
         paint.setTypeface(Typeface.DEFAULT);
         // Estimate appropriate language name text size to fit in maxTextWidth.
-        String language = Utils.getFullDisplayName(locale, true);
+        String language = StringUtils.getFullDisplayName(locale, true);
         int textWidth = getTextWidth(paint, language, origTextSize);
         // Assuming text width and text size are proportional to each other.
         float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
@@ -822,7 +825,7 @@
 
         final boolean useShortName;
         if (useMiddleName) {
-            language = Utils.getMiddleDisplayLanguage(locale);
+            language = StringUtils.getMiddleDisplayLanguage(locale);
             textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
             useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
@@ -832,7 +835,7 @@
         }
 
         if (useShortName) {
-            language = Utils.getShortDisplayLanguage(locale);
+            language = StringUtils.getShortDisplayLanguage(locale);
             textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 9f735cf..904a81d 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -22,7 +22,7 @@
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.StringUtils;
 
 public class MoreKeysKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
@@ -301,7 +301,7 @@
             for (String moreKeySpec : parentKey.mMoreKeys) {
                 final String label = KeySpecParser.getLabel(moreKeySpec);
                 // If the label is single letter, minKeyWidth is enough to hold the label.
-                if (label != null && Utils.codePointCount(label) > 1) {
+                if (label != null && StringUtils.codePointCount(label) > 1) {
                     if (paint == null) {
                         paint = new Paint();
                         paint.setAntiAlias(true);
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 721ea13..1480bba 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -19,7 +19,7 @@
 import android.graphics.Rect;
 
 import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.JniUtils;
 import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
 import java.util.Arrays;
@@ -84,7 +84,7 @@
 
     private long mNativeProximityInfo;
     static {
-        Utils.loadNativeLibrary();
+        JniUtils.loadNativeLibrary();
     }
 
     private native long setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index e3fea3d..0aba813 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -22,7 +22,7 @@
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -150,7 +150,7 @@
         }
         final String outputText = getOutputTextInternal(moreKeySpec);
         if (outputText != null) {
-            if (Utils.codePointCount(outputText) == 1) {
+            if (StringUtils.codePointCount(outputText) == 1) {
                 // If output text is one code point, it should be treated as a code.
                 // See {@link #getCode(Resources, String)}.
                 return null;
@@ -165,7 +165,7 @@
             throw new KeySpecParserError("Empty label: " + moreKeySpec);
         }
         // Code is automatically generated for one letter label. See {@link getCode()}.
-        return (Utils.codePointCount(label) == 1) ? null : label;
+        return (StringUtils.codePointCount(label) == 1) ? null : label;
     }
 
     public static int getCode(Resources res, String moreKeySpec) {
@@ -184,14 +184,14 @@
         if (outputText != null) {
             // If output text is one code point, it should be treated as a code.
             // See {@link #getOutputText(String)}.
-            if (Utils.codePointCount(outputText) == 1) {
+            if (StringUtils.codePointCount(outputText) == 1) {
                 return outputText.codePointAt(0);
             }
             return Keyboard.CODE_OUTPUT_TEXT;
         }
         final String label = getLabel(moreKeySpec);
         // Code is automatically generated for one letter label.
-        if (Utils.codePointCount(label) == 1) {
+        if (StringUtils.codePointCount(label) == 1) {
             return label.codePointAt(0);
         }
         return Keyboard.CODE_OUTPUT_TEXT;
@@ -393,7 +393,7 @@
         if (size == 0) {
             return null;
         }
-        if (Utils.codePointCount(text) == 1) {
+        if (StringUtils.codePointCount(text) == 1) {
             return text.codePointAt(0) == COMMA ? null : new String[] { text };
         }
 
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 1cbdbd6..9c5ccc7 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -16,9 +16,7 @@
 
 package com.android.inputmethod.latin;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
 import android.media.AudioManager;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
@@ -32,7 +30,7 @@
  * It offers a consistent and simple interface that allows LatinIME to forget about the
  * complexity of settings and the like.
  */
-public class AudioAndHapticFeedbackManager extends BroadcastReceiver {
+public class AudioAndHapticFeedbackManager {
     final private SettingsValues mSettingsValues;
     final private AudioManager mAudioManager;
     final private VibratorCompatWrapper mVibrator;
@@ -100,13 +98,7 @@
         }
     }
 
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        // The following test is supposedly useless since we only listen for the ringer event.
-        // Still, it's a good safety measure.
-        if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
-            mSoundOn = reevaluateIfSoundIsOn();
-        }
+    public void onRingerModeChanged() {
+        mSoundOn = reevaluateIfSoundIsOn();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 90ced60..31ff4e7 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -104,7 +104,7 @@
     }
 
     static {
-        Utils.loadNativeLibrary();
+        JniUtils.loadNativeLibrary();
     }
 
     private native long openNative(String sourceDir, long dictOffset, long dictSize,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 1607f86..7a81f7b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -164,7 +164,7 @@
         final Resources res = context.getResources();
         final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
 
-        final int resourceId = Utils.getMainDictionaryResourceId(res);
+        final int resourceId = getMainDictionaryResourceId(res);
         final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
         final boolean hasDictionary = isFullDictionary(afd);
         try {
@@ -182,7 +182,7 @@
         final Resources res = context.getResources();
         final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
 
-        final int resourceId = Utils.getMainDictionaryResourceId(res);
+        final int resourceId = getMainDictionaryResourceId(res);
         final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
         final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
                 ? afd.getLength()
@@ -209,4 +209,14 @@
     protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
         return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
     }
+
+    /**
+     * Returns a main dictionary resource id
+     * @return main dictionary resource id
+     */
+    public static int getMainDictionaryResourceId(Resources res) {
+        final String MAIN_DIC_NAME = "main";
+        String packageName = LatinIME.class.getPackage().getName();
+        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
new file mode 100644
index 0000000..4808b86
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.Log;
+
+import com.android.inputmethod.latin.define.JniLibName;
+
+public class JniUtils {
+    private static final String TAG = JniUtils.class.getSimpleName();
+
+    private JniUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void loadNativeLibrary() {
+        try {
+            System.loadLibrary(JniLibName.JNI_LIB_NAME);
+        } catch (UnsatisfiedLinkError ule) {
+            Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME);
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException(
+                        "Could not load native library " + JniLibName.JNI_LIB_NAME);
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7a081c0..73a9689 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -43,8 +43,8 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
@@ -532,13 +532,10 @@
         // Also receive installation and removal of a dictionary pack.
         final IntentFilter filter = new IntentFilter();
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
         mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
 
-        final IntentFilter ringerModeFilter = new IntentFilter();
-        ringerModeFilter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
-        registerReceiver(mFeedbackManager, ringerModeFilter);
-
         final IntentFilter packageFilter = new IntentFilter();
         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -573,7 +570,7 @@
             oldContactsDictionary = null;
         }
 
-        int mainDicResId = Utils.getMainDictionaryResourceId(res);
+        int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res);
         mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
         if (mSettingsValues.mAutoCorrectEnabled) {
             mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
@@ -633,7 +630,7 @@
     /* package private */ void resetSuggestMainDict() {
         final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
         final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
-        int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+        int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(mResources);
         mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
     }
 
@@ -644,7 +641,6 @@
             mSuggest = null;
         }
         unregisterReceiver(mReceiver);
-        unregisterReceiver(mFeedbackManager);
         unregisterReceiver(mDictionaryPackInstallReceiver);
         mVoiceProxy.destroy();
         LatinImeLogger.commit();
@@ -742,12 +738,12 @@
                     + String.format("inputType=0x%08x imeOptions=0x%08x",
                             editorInfo.inputType, editorInfo.imeOptions));
         }
-        if (Utils.inPrivateImeOptions(null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) {
+        if (StringUtils.inPrivateImeOptions(null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) {
             Log.w(TAG, "Deprecated private IME option specified: "
                     + editorInfo.privateImeOptions);
             Log.w(TAG, "Use " + getPackageName() + "." + IME_OPTION_NO_MICROPHONE + " instead");
         }
-        if (Utils.inPrivateImeOptions(getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) {
+        if (StringUtils.inPrivateImeOptions(getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) {
             Log.w(TAG, "Deprecated private IME option specified: "
                     + editorInfo.privateImeOptions);
             Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
@@ -1203,7 +1199,7 @@
         if (ic == null) return false;
         final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
-                && Utils.canBeFollowedByPeriod(lastThree.charAt(0))
+                && StringUtils.canBeFollowedByPeriod(lastThree.charAt(0))
                 && lastThree.charAt(1) == Keyboard.CODE_SPACE
                 && lastThree.charAt(2) == Keyboard.CODE_SPACE
                 && mHandler.isAcceptingDoubleSpaces()) {
@@ -1243,7 +1239,8 @@
         if (isShowingOptionDialog()) return;
         if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
             showSubtypeSelectorAndSettings();
-        } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(false /* exclude aux subtypes */)) {
+        } else if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(
+                false /* exclude aux subtypes */)) {
             showOptionsMenu();
         } else {
             launchSettings();
@@ -1259,7 +1256,7 @@
         if (isShowingOptionDialog()) return false;
         switch (requestCode) {
         case CODE_SHOW_INPUT_METHOD_PICKER:
-            if (Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+            if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 mImm.showInputMethodPicker();
                 return true;
             }
@@ -1291,7 +1288,7 @@
         final IBinder token = getWindow().getWindow().getAttributes().token;
         if (mShouldSwitchToLastSubtype) {
             final InputMethodSubtypeCompatWrapper lastSubtype = mImm.getLastInputMethodSubtype();
-            final boolean lastSubtypeBelongsToThisIme = Utils.checkIfSubtypeBelongsToThisIme(
+            final boolean lastSubtypeBelongsToThisIme = SubtypeUtils.checkIfSubtypeBelongsToThisIme(
                     this, lastSubtype);
             if ((includesOtherImes || lastSubtypeBelongsToThisIme)
                     && mImm.switchToLastInputMethod(token)) {
@@ -1872,7 +1869,7 @@
                 builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
             }
         }
-        if (Utils.shouldBlockAutoCorrectionBySafetyNet(builder, mSuggest)) {
+        if (Suggest.shouldBlockAutoCorrectionBySafetyNet(builder, mSuggest)) {
             builder.setShouldBlockAutoCorrectionBySafetyNet();
         }
         showSuggestions(builder.build(), typedWord);
@@ -2351,6 +2348,8 @@
             final String action = intent.getAction();
             if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                 mSubtypeSwitcher.onNetworkStateChanged(intent);
+            } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+                mFeedbackManager.onRingerModeChanged();
             }
         }
     };
@@ -2413,7 +2412,7 @@
                 switch (position) {
                 case 0:
                     Intent intent = CompatUtils.getInputLanguageSelectionIntent(
-                            Utils.getInputMethodId(getPackageName()),
+                            SubtypeUtils.getInputMethodId(getPackageName()),
                             Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 305cef2..72391f3 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -343,7 +343,7 @@
     @Override
     public boolean onPreferenceClick(Preference pref) {
         if (pref == mInputLanguageSelection) {
-            final String imeId = Utils.getInputMethodId(
+            final String imeId = SubtypeUtils.getInputMethodId(
                     getActivityInternal().getApplicationInfo().packageName);
             startActivity(CompatUtils.getInputLanguageSelectionIntent(imeId, 0));
             return true;
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 69e45f6..abd1dc6 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -326,9 +326,9 @@
             return false;
         }
         if (mIncludesOtherImesInLanguageSwitchList) {
-            return Utils.hasMultipleEnabledIMEsOrSubtypes(/* include aux subtypes */false);
+            return SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(/* include aux subtypes */false);
         } else {
-            return Utils.hasMultipleEnabledSubtypesInThisIme(
+            return SubtypeUtils.hasMultipleEnabledSubtypesInThisIme(
                     context, /* include aux subtypes */false);
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
new file mode 100644
index 0000000..81c3b4e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class StringUtils {
+    private StringUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static boolean canBeFollowedByPeriod(final int codePoint) {
+        // TODO: Check again whether there really ain't a better way to check this.
+        // TODO: This should probably be language-dependant...
+        return Character.isLetterOrDigit(codePoint)
+                || codePoint == Keyboard.CODE_SINGLE_QUOTE
+                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+    }
+
+    public static int codePointCount(String text) {
+        if (TextUtils.isEmpty(text)) return 0;
+        return text.codePointCount(0, text.length());
+    }
+
+    public static boolean containsInCsv(String key, String csv) {
+        if (csv == null)
+            return false;
+        for (String option : csv.split(",")) {
+            if (option.equals(key))
+                return true;
+        }
+        return false;
+    }
+
+    public static boolean inPrivateImeOptions(String packageName, String key,
+            EditorInfo editorInfo) {
+        if (editorInfo == null)
+            return false;
+        return containsInCsv(packageName != null ? packageName + "." + key : key,
+                editorInfo.privateImeOptions);
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the character.
+     * @param a first character to check
+     * @param b second character to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(char a, char b) {
+        // Some language, such as Turkish, need testing both cases.
+        return a == b
+                || Character.toLowerCase(a) == Character.toLowerCase(b)
+                || Character.toUpperCase(a) == Character.toUpperCase(b);
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if they are
+     * both null.
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+        if (a == b)
+            return true;  // including both a and b are null.
+        if (a == null || b == null)
+            return false;
+        final int length = a.length();
+        if (length != b.length())
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+     * and b is zero length.
+     * @param a CharSequence to check
+     * @param b character array to check
+     * @param offset start offset of array b
+     * @param length length of characters in array b
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     * @throws IndexOutOfBoundsException
+     *   if {@code offset < 0 || length < 0 || offset + length > data.length}.
+     * @throws NullPointerException if {@code b == null}.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+        if (offset < 0 || length < 0 || length > b.length - offset)
+            throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+                    + " length=" + length);
+        if (a == null)
+            return length == 0;  // including a is null and b is zero length.
+        if (a.length() != length)
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Remove duplicates from an array of strings.
+     *
+     * This method will always keep the first occurence of all strings at their position
+     * in the array, removing the subsequent ones.
+     */
+    public static void removeDupes(final ArrayList<CharSequence> suggestions) {
+        if (suggestions.size() < 2) return;
+        int i = 1;
+        // Don't cache suggestions.size(), since we may be removing items
+        while (i < suggestions.size()) {
+            final CharSequence cur = suggestions.get(i);
+            // Compare each suggestion with each previous suggestion
+            for (int j = 0; j < i; j++) {
+                CharSequence previous = suggestions.get(j);
+                if (TextUtils.equals(cur, previous)) {
+                    removeFromSuggestions(suggestions, i);
+                    i--;
+                    break;
+                }
+            }
+            i++;
+        }
+    }
+
+    private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
+            final int index) {
+        final CharSequence garbage = suggestions.remove(index);
+        if (garbage instanceof StringBuilder) {
+            StringBuilderPool.recycle((StringBuilder)garbage);
+        }
+    }
+
+    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
+        if (returnsNameInThisLocale) {
+            return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+        } else {
+            return toTitleCase(locale.getDisplayName(), locale);
+        }
+    }
+
+    public static String getDisplayLanguage(Locale locale) {
+        return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+    }
+
+    public static String getMiddleDisplayLanguage(Locale locale) {
+        return toTitleCase((LocaleUtils.constructLocaleFromString(
+                locale.getLanguage()).getDisplayLanguage(locale)), locale);
+    }
+
+    public static String getShortDisplayLanguage(Locale locale) {
+        return toTitleCase(locale.getLanguage(), locale);
+    }
+
+    public static String toTitleCase(String s, Locale locale) {
+        if (s.length() <= 1) {
+            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
+            return s;
+        }
+        // TODO: fix the bugs below
+        // - This does not work for Greek, because it returns upper case instead of title case.
+        // - It does not work for Serbian, because it fails to account for the "lj" character,
+        // which should be "Lj" in title case and "LJ" in upper case.
+        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
+        // are two different characters but both should be capitalized as "IJ" as if they were
+        // a single letter.
+        // - It also does not work with unicode surrogate code points.
+        return s.toUpperCase(locale).charAt(0) + s.substring(1);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f577816..ffbbf9b 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -512,7 +512,7 @@
     }
 
     public String getInputLanguageName() {
-        return Utils.getDisplayLanguage(getInputLocale());
+        return StringUtils.getDisplayLanguage(getInputLocale());
     }
 
     /////////////////////////////
diff --git a/java/src/com/android/inputmethod/latin/SubtypeUtils.java b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
new file mode 100644
index 0000000..cb2bcf4
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SubtypeUtils {
+    private SubtypeUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    // TODO: Cache my InputMethodInfo and/or InputMethodSubtype list.
+    public static boolean checkIfSubtypeBelongsToThisIme(Context context,
+            InputMethodSubtypeCompatWrapper ims) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        final InputMethodInfoCompatWrapper myImi = getInputMethodInfo(context.getPackageName());
+        final List<InputMethodSubtypeCompatWrapper> subtypes =
+                imm.getEnabledInputMethodSubtypeList(myImi, true);
+        for (final InputMethodSubtypeCompatWrapper subtype : subtypes) {
+            if (subtype.equals(ims)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean hasMultipleEnabledIMEsOrSubtypes(
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
+    }
+
+    public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodInfoCompatWrapper myImi = getInputMethodInfo(context.getPackageName());
+        final List<InputMethodInfoCompatWrapper> imiList = Collections.singletonList(myImi);
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
+    }
+
+    private static boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
+            List<InputMethodInfoCompatWrapper> imiList) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        // Number of the filtered IMEs
+        int filteredImisCount = 0;
+
+        for (InputMethodInfoCompatWrapper imi : imiList) {
+            // We can return true immediately after we find two or more filtered IMEs.
+            if (filteredImisCount > 1) return true;
+            final List<InputMethodSubtypeCompatWrapper> subtypes =
+                    imm.getEnabledInputMethodSubtypeList(imi, true);
+            // IMEs that have no subtypes should be counted.
+            if (subtypes.isEmpty()) {
+                ++filteredImisCount;
+                continue;
+            }
+
+            int auxCount = 0;
+            for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
+                if (subtype.isAuxiliary()) {
+                    ++auxCount;
+                }
+            }
+            final int nonAuxCount = subtypes.size() - auxCount;
+
+            // IMEs that have one or more non-auxiliary subtypes should be counted.
+            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+            // subtypes should be counted as well.
+            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+                ++filteredImisCount;
+                continue;
+            }
+        }
+
+        if (filteredImisCount > 1) {
+            return true;
+        }
+        final List<InputMethodSubtypeCompatWrapper> subtypes =
+                imm.getEnabledInputMethodSubtypeList(null, true);
+        int keyboardCount = 0;
+        // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
+        // both explicitly and implicitly enabled input method subtype.
+        // (The current IME should be LatinIME.)
+        for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
+            if (SubtypeSwitcher.KEYBOARD_MODE.equals(subtype.getMode())) {
+                ++keyboardCount;
+            }
+        }
+        return keyboardCount > 1;
+    }
+
+    public static String getInputMethodId(String packageName) {
+        return getInputMethodInfo(packageName).getId();
+    }
+
+    public static InputMethodInfoCompatWrapper getInputMethodInfo(String packageName) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) {
+            throw new RuntimeException("Input method manager not found");
+        }
+
+        for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
+            if (imi.getPackageName().equals(packageName))
+                return imi;
+        }
+        throw new RuntimeException("Can not find input method id for " + packageName);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 5a4ddd9..889d505 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -108,6 +108,8 @@
     private boolean mIsAllUpperCase;
     private int mTrailingSingleQuotesCount;
 
+    private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
+
     public Suggest(final Context context, final int dictionaryResId, final Locale locale) {
         initAsynchronously(context, dictionaryResId, locale);
     }
@@ -237,20 +239,6 @@
         StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
     }
 
-    /**
-     * Returns a object which represents suggested words that match the list of character codes
-     * passed in. This object contents will be overwritten the next time this function is called.
-     * @param wordComposer contains what is currently being typed
-     * @param prevWordForBigram previous word (used only for bigram)
-     * @return suggested words object.
-     */
-    public SuggestedWords getSuggestions(final WordComposer wordComposer,
-            final CharSequence prevWordForBigram, final ProximityInfo proximityInfo,
-            final int correctionMode) {
-        return getSuggestedWordBuilder(wordComposer, prevWordForBigram,
-                proximityInfo, correctionMode).build();
-    }
-
     private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
         if (TextUtils.isEmpty(word) || !(all || first)) return word;
         final int wordLength = word.length();
@@ -383,7 +371,7 @@
         if (typedWord != null) {
             mSuggestions.add(0, typedWord.toString());
         }
-        Utils.removeDupes(mSuggestions);
+        StringUtils.removeDupes(mSuggestions);
 
         if (DBG) {
             final CharSequence autoCorrectionSuggestion = mSuggestions.get(0);
@@ -438,7 +426,7 @@
         int pos = 0;
 
         // Check if it's the same word, only caps are different
-        if (Utils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
+        if (StringUtils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
             // TODO: remove this surrounding if clause and move this logic to
             // getSuggestedWordBuilder.
             if (suggestions.size() > 0) {
@@ -447,7 +435,7 @@
                 // frequency to determine the insertion position. This does not ensure strictly
                 // correct ordering, but ensures the top score is on top which is enough for
                 // removing duplicates correctly.
-                if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
+                if (StringUtils.equalsIgnoreCase(currentHighestWord, word, offset, length)
                         && score <= sortedScores[0]) {
                     pos = 1;
                 }
@@ -562,4 +550,46 @@
         }
         mMainDict = null;
     }
+
+    // TODO: Resolve the inconsistencies between the native auto correction algorithms and
+    // this safety net
+    public static boolean shouldBlockAutoCorrectionBySafetyNet(
+            SuggestedWords.Builder suggestedWordsBuilder, Suggest suggest) {
+        // Safety net for auto correction.
+        // Actually if we hit this safety net, it's actually a bug.
+        if (suggestedWordsBuilder.size() <= 1 || suggestedWordsBuilder.isTypedWordValid()) {
+            return false;
+        }
+        // If user selected aggressive auto correction mode, there is no need to use the safety
+        // net.
+        if (suggest.isAggressiveAutoCorrectionMode()) {
+            return false;
+        }
+        final CharSequence typedWord = suggestedWordsBuilder.getWord(0);
+        // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
+        // we should not use net because relatively edit distance can be big.
+        if (typedWord.length() < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) {
+            return false;
+        }
+        final CharSequence suggestionWord = suggestedWordsBuilder.getWord(1);
+        final int typedWordLength = typedWord.length();
+        final int maxEditDistanceOfNativeDictionary =
+                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
+        final int distance = BinaryDictionary.editDistance(
+                typedWord.toString(), suggestionWord.toString());
+        if (DBG) {
+            Log.d(TAG, "Autocorrected edit distance = " + distance
+                    + ", " + maxEditDistanceOfNativeDictionary);
+        }
+        if (distance > maxEditDistanceOfNativeDictionary) {
+            if (DBG) {
+                Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestionWord);
+                Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
+                        + "Turning off auto-correction.");
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index a8679e0..f8dd5ae 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -27,19 +26,9 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
-import android.text.InputType;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
-import android.view.inputmethod.EditorInfo;
-
-import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.define.JniLibName;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -51,20 +40,11 @@
 import java.io.PrintWriter;
 import java.nio.channels.FileChannel;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Date;
-import java.util.List;
-import java.util.Locale;
 
 public class Utils {
-    private static final String TAG = Utils.class.getSimpleName();
-    private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
-    private static boolean DBG = LatinImeLogger.sDBG;
-    private static boolean DBG_EDIT_DISTANCE = false;
-
     private Utils() {
-        // Intentional empty constructor for utility class.
+        // This utility class is not publicly instantiable.
     }
 
     /**
@@ -118,166 +98,6 @@
         }
     }
 
-    // TODO: Move InputMethodSubtype related utility methods to its own utility class.
-    // TODO: Cache my InputMethodInfo and/or InputMethodSubtype list.
-    public static boolean checkIfSubtypeBelongsToThisIme(Context context,
-            InputMethodSubtypeCompatWrapper ims) {
-        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
-        if (imm == null) return false;
-
-        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
-                context.getPackageName());
-        final List<InputMethodSubtypeCompatWrapper> subtypes =
-                imm.getEnabledInputMethodSubtypeList(myImi, true);
-        for (final InputMethodSubtypeCompatWrapper subtype : subtypes) {
-            if (subtype.equals(ims)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean hasMultipleEnabledIMEsOrSubtypes(
-            final boolean shouldIncludeAuxiliarySubtypes) {
-        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
-        if (imm == null) return false;
-
-        final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
-        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
-    }
-
-    public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
-            final boolean shouldIncludeAuxiliarySubtypes) {
-        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
-                context.getPackageName());
-        final List<InputMethodInfoCompatWrapper> imiList = Collections.singletonList(myImi);
-        return Utils.hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
-    }
-
-    private static boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
-            List<InputMethodInfoCompatWrapper> imiList) {
-        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
-        if (imm == null) return false;
-
-        // Number of the filtered IMEs
-        int filteredImisCount = 0;
-
-        for (InputMethodInfoCompatWrapper imi : imiList) {
-            // We can return true immediately after we find two or more filtered IMEs.
-            if (filteredImisCount > 1) return true;
-            final List<InputMethodSubtypeCompatWrapper> subtypes =
-                    imm.getEnabledInputMethodSubtypeList(imi, true);
-            // IMEs that have no subtypes should be counted.
-            if (subtypes.isEmpty()) {
-                ++filteredImisCount;
-                continue;
-            }
-
-            int auxCount = 0;
-            for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
-                if (subtype.isAuxiliary()) {
-                    ++auxCount;
-                }
-            }
-            final int nonAuxCount = subtypes.size() - auxCount;
-
-            // IMEs that have one or more non-auxiliary subtypes should be counted.
-            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
-            // subtypes should be counted as well.
-            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
-                ++filteredImisCount;
-                continue;
-            }
-        }
-
-        if (filteredImisCount > 1) {
-            return true;
-        }
-        final List<InputMethodSubtypeCompatWrapper> subtypes =
-                imm.getEnabledInputMethodSubtypeList(null, true);
-        int keyboardCount = 0;
-        // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
-        // both explicitly and implicitly enabled input method subtype.
-        // (The current IME should be LatinIME.)
-        for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
-            if (SubtypeSwitcher.KEYBOARD_MODE.equals(subtype.getMode())) {
-                ++keyboardCount;
-            }
-        }
-        return keyboardCount > 1;
-    }
-
-    public static String getInputMethodId(String packageName) {
-        return getInputMethodInfo(packageName).getId();
-    }
-
-    public static InputMethodInfoCompatWrapper getInputMethodInfo(String packageName) {
-        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
-        if (imm == null) {
-            throw new RuntimeException("Input method manager not found");
-        }
-
-        for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
-            if (imi.getPackageName().equals(packageName))
-                return imi;
-        }
-        throw new RuntimeException("Can not find input method id for " + packageName);
-    }
-
-    // TODO: Resolve the inconsistencies between the native auto correction algorithms and
-    // this safety net
-    public static boolean shouldBlockAutoCorrectionBySafetyNet(
-            SuggestedWords.Builder suggestedWordsBuilder, Suggest suggest) {
-        // Safety net for auto correction.
-        // Actually if we hit this safety net, it's actually a bug.
-        if (suggestedWordsBuilder.size() <= 1 || suggestedWordsBuilder.isTypedWordValid()) {
-            return false;
-        }
-        // If user selected aggressive auto correction mode, there is no need to use the safety
-        // net.
-        if (suggest.isAggressiveAutoCorrectionMode()) {
-            return false;
-        }
-        final CharSequence typedWord = suggestedWordsBuilder.getWord(0);
-        // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
-        // we should not use net because relatively edit distance can be big.
-        if (typedWord.length() < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
-            return false;
-        }
-        final CharSequence suggestionWord = suggestedWordsBuilder.getWord(1);
-        final int typedWordLength = typedWord.length();
-        final int maxEditDistanceOfNativeDictionary =
-                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
-        final int distance = BinaryDictionary.editDistance(
-                typedWord.toString(), suggestionWord.toString());
-        if (DBG) {
-            Log.d(TAG, "Autocorrected edit distance = " + distance
-                    + ", " + maxEditDistanceOfNativeDictionary);
-        }
-        if (distance > maxEditDistanceOfNativeDictionary) {
-            if (DBG) {
-                Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestionWord);
-                Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
-                        + "Turning off auto-correction.");
-            }
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public static boolean canBeFollowedByPeriod(final int codePoint) {
-        // TODO: Check again whether there really ain't a better way to check this.
-        // TODO: This should probably be language-dependant...
-        return Character.isLetterOrDigit(codePoint)
-                || codePoint == Keyboard.CODE_SINGLE_QUOTE
-                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
-                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
-                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
-                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
-                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
-    }
-
     /* package */ static class RingCharBuffer {
         private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
         private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -600,147 +420,6 @@
         }
     }
 
-    // TODO: Move this method to KeyboardSet class.
-    public static int getKeyboardMode(EditorInfo editorInfo) {
-        if (editorInfo == null)
-            return KeyboardId.MODE_TEXT;
-
-        final int inputType = editorInfo.inputType;
-        final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-
-        switch (inputType & InputType.TYPE_MASK_CLASS) {
-        case InputType.TYPE_CLASS_NUMBER:
-            return KeyboardId.MODE_NUMBER;
-        case InputType.TYPE_CLASS_DATETIME:
-            switch (variation) {
-            case InputType.TYPE_DATETIME_VARIATION_DATE:
-                return KeyboardId.MODE_DATE;
-            case InputType.TYPE_DATETIME_VARIATION_TIME:
-                return KeyboardId.MODE_TIME;
-            default: // InputType.TYPE_DATETIME_VARIATION_NORMAL
-                return KeyboardId.MODE_DATETIME;
-            }
-        case InputType.TYPE_CLASS_PHONE:
-            return KeyboardId.MODE_PHONE;
-        case InputType.TYPE_CLASS_TEXT:
-            if (InputTypeCompatUtils.isEmailVariation(variation)) {
-                return KeyboardId.MODE_EMAIL;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
-                return KeyboardId.MODE_URL;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
-                return KeyboardId.MODE_IM;
-            } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
-                return KeyboardId.MODE_TEXT;
-            } else {
-                return KeyboardId.MODE_TEXT;
-            }
-        default:
-            return KeyboardId.MODE_TEXT;
-        }
-    }
-
-    public static boolean containsInCsv(String key, String csv) {
-        if (csv == null)
-            return false;
-        for (String option : csv.split(",")) {
-            if (option.equals(key))
-                return true;
-        }
-        return false;
-    }
-
-    public static boolean inPrivateImeOptions(String packageName, String key,
-            EditorInfo editorInfo) {
-        if (editorInfo == null)
-            return false;
-        return containsInCsv(packageName != null ? packageName + "." + key : key,
-                editorInfo.privateImeOptions);
-    }
-
-    /**
-     * Returns a main dictionary resource id
-     * @return main dictionary resource id
-     */
-    public static int getMainDictionaryResourceId(Resources res) {
-        final String MAIN_DIC_NAME = "main";
-        String packageName = LatinIME.class.getPackage().getName();
-        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
-    }
-
-    public static void loadNativeLibrary() {
-        try {
-            System.loadLibrary(JniLibName.JNI_LIB_NAME);
-        } catch (UnsatisfiedLinkError ule) {
-            Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME);
-            if (LatinImeLogger.sDBG) {
-                throw new RuntimeException(
-                        "Could not load native library " + JniLibName.JNI_LIB_NAME);
-            }
-        }
-    }
-
-    /**
-     * Returns true if a and b are equal ignoring the case of the character.
-     * @param a first character to check
-     * @param b second character to check
-     * @return {@code true} if a and b are equal, {@code false} otherwise.
-     */
-    public static boolean equalsIgnoreCase(char a, char b) {
-        // Some language, such as Turkish, need testing both cases.
-        return a == b
-                || Character.toLowerCase(a) == Character.toLowerCase(b)
-                || Character.toUpperCase(a) == Character.toUpperCase(b);
-    }
-
-    /**
-     * Returns true if a and b are equal ignoring the case of the characters, including if they are
-     * both null.
-     * @param a first CharSequence to check
-     * @param b second CharSequence to check
-     * @return {@code true} if a and b are equal, {@code false} otherwise.
-     */
-    public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
-        if (a == b)
-            return true;  // including both a and b are null.
-        if (a == null || b == null)
-            return false;
-        final int length = a.length();
-        if (length != b.length())
-            return false;
-        for (int i = 0; i < length; i++) {
-            if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
-                return false;
-        }
-        return true;
-    }
-
-    /**
-     * Returns true if a and b are equal ignoring the case of the characters, including if a is null
-     * and b is zero length.
-     * @param a CharSequence to check
-     * @param b character array to check
-     * @param offset start offset of array b
-     * @param length length of characters in array b
-     * @return {@code true} if a and b are equal, {@code false} otherwise.
-     * @throws IndexOutOfBoundsException
-     *   if {@code offset < 0 || length < 0 || offset + length > data.length}.
-     * @throws NullPointerException if {@code b == null}.
-     */
-    public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
-        if (offset < 0 || length < 0 || length > b.length - offset)
-            throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
-                    + " length=" + length);
-        if (a == null)
-            return length == 0;  // including a is null and b is zero length.
-        if (a.length() != length)
-            return false;
-        for (int i = 0; i < length; i++) {
-            if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
-                return false;
-        }
-        return true;
-    }
-
     public static float getDipScale(Context context) {
         final float scale = context.getResources().getDisplayMetrics().density;
         return scale;
@@ -751,76 +430,6 @@
         return (int) (dip * scale + 0.5);
     }
 
-    /**
-     * Remove duplicates from an array of strings.
-     *
-     * This method will always keep the first occurence of all strings at their position
-     * in the array, removing the subsequent ones.
-     */
-    public static void removeDupes(final ArrayList<CharSequence> suggestions) {
-        if (suggestions.size() < 2) return;
-        int i = 1;
-        // Don't cache suggestions.size(), since we may be removing items
-        while (i < suggestions.size()) {
-            final CharSequence cur = suggestions.get(i);
-            // Compare each suggestion with each previous suggestion
-            for (int j = 0; j < i; j++) {
-                CharSequence previous = suggestions.get(j);
-                if (TextUtils.equals(cur, previous)) {
-                    removeFromSuggestions(suggestions, i);
-                    i--;
-                    break;
-                }
-            }
-            i++;
-        }
-    }
-
-    private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
-            final int index) {
-        final CharSequence garbage = suggestions.remove(index);
-        if (garbage instanceof StringBuilder) {
-            StringBuilderPool.recycle((StringBuilder)garbage);
-        }
-    }
-
-    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
-        if (returnsNameInThisLocale) {
-            return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
-        } else {
-            return toTitleCase(locale.getDisplayName(), locale);
-        }
-    }
-
-    public static String getDisplayLanguage(Locale locale) {
-        return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
-    }
-
-    public static String getMiddleDisplayLanguage(Locale locale) {
-        return toTitleCase((LocaleUtils.constructLocaleFromString(
-                locale.getLanguage()).getDisplayLanguage(locale)), locale);
-    }
-
-    public static String getShortDisplayLanguage(Locale locale) {
-        return toTitleCase(locale.getLanguage(), locale);
-    }
-
-    public static String toTitleCase(String s, Locale locale) {
-        if (s.length() <= 1) {
-            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
-            return s;
-        }
-        // TODO: fix the bugs below
-        // - This does not work for Greek, because it returns upper case instead of title case.
-        // - It does not work for Serbian, because it fails to account for the "lj" character,
-        // which should be "Lj" in title case and "LJ" in upper case.
-        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
-        // are two different characters but both should be capitalized as "IJ" as if they were
-        // a single letter.
-        // - It also does not work with unicode surrogate code points.
-        return s.toUpperCase(locale).charAt(0) + s.substring(1);
-    }
-
     public static class Stats {
         public static void onNonSeparator(final char code, final int x,
                 final int y) {
@@ -845,9 +454,4 @@
             LatinImeLogger.logOnAutoCorrectionCancelled();
         }
     }
-
-    public static int codePointCount(String text) {
-        if (TextUtils.isEmpty(text)) return 0;
-        return text.codePointCount(0, text.length());
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 755c75b..35a5c0f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -37,9 +37,9 @@
 import com.android.inputmethod.latin.Flag;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.WhitelistDictionary;
 import com.android.inputmethod.latin.WordComposer;
 
@@ -47,11 +47,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.HashSet;
 
 /**
  * Service for spell checking, using LatinIME's dictionaries and mechanisms.
@@ -316,7 +316,7 @@
                     }
                 }
                 Collections.reverse(mSuggestions);
-                Utils.removeDupes(mSuggestions);
+                StringUtils.removeDupes(mSuggestions);
                 if (CAPITALIZE_ALL == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // get(i) returns a CharSequence which is actually a String so .toString()
@@ -326,7 +326,7 @@
                 } else if (CAPITALIZE_FIRST == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // Likewise
-                        mSuggestions.set(i, Utils.toTitleCase(mSuggestions.get(i).toString(),
+                        mSuggestions.set(i, StringUtils.toTitleCase(mSuggestions.get(i).toString(),
                                 locale));
                     }
                 }
@@ -396,7 +396,7 @@
         final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
                 SpellCheckerProximityInfo.getProximityForScript(script));
         final Resources resources = getResources();
-        final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+        final int fallbackResourceId = DictionaryFactory.getMainDictionaryResourceId(resources);
         final DictionaryCollection dictionaryCollection =
                 DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId,
                         USE_FULL_EDIT_DISTANCE_FLAG_ARRAY);