diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 9dd0a599de551afe4a6ccaa37efa84ed94595d16..bf5f20158af215d20d67ce81d61aed770f9a9f81 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -32,7 +32,7 @@ import android.view.inputmethod.InputMethodManager;
 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 @@ public class InputMethodManagerCompatWrapper {
     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 @@ public class InputMethodManagerCompatWrapper {
 
         // 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 c1c6d31cdf01f06a2cbcc423dd3c94532d8dcbb8..87d1c118b451466866e37c1e119f6e91d8c92f80 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;
 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 @@ public class VoiceProxy implements VoiceInput.UiListener {
 
     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 e75e14861e104d84d9bc1ae3ad208a07d05e0d7f..421ee65295e48442055a1c133347a2949105cbec 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.preference.PreferenceManager;
 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 @@ public class InputLanguageSelection extends PreferenceActivity {
 
             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 71d15dc3db70f143af8afab8878913fbd6ba64e0..ff8b1abce9abd6360b4803f902fbd62b79f11912 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.ImageView;
 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 @@ public class RecognitionView {
                 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 f3923834bf5adbd82165de4e7d6d5c21039856bb..6b4de184fe1587df24ed68bc3d993bd3d4cc8c11 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;
 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 @@ public class Key {
         // 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 @@ public class Key {
                 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 @@ public class Key {
         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 class Key {
     }
 
     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 731aaf7c52f27c81135a7c33f7bfd8dbd544c665..5ac6d03a838bdb25141caa5ae4e5977f8545ed04 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.Configuration;
 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 @@ public class KeyboardSet {
             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 @@ public class KeyboardSet {
         // 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 class KeyboardSet {
         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 @@ public class KeyboardSet {
                 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 78e0ee23021fca6a2ad3adc7948e08e809c282f6..decd73d458b904492eb5dcc0435a23f6e2b42892 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.compat.FrameLayoutCompatUtils;
 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
         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 afc4932e93bdd1cd578b189ceaf03251a85b9549..0a0307500405e026303bfa845af783cdb85eb4b6 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.LatinIME;
 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
 
             // 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
         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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
 
         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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
         }
 
         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 9f735cff7f4696db0cd08f4b6cb2cc75cd9d11fd..904a81de41c224292ec33c0a68b03bf76fa97154 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -22,7 +22,7 @@ import android.graphics.drawable.Drawable;
 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 @@ public class MoreKeysKeyboard extends Keyboard {
             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 f96f71e8a77c0c166ac19c2b00efdf714175384d..5825093242550b7f43518394f61e9d980403df5f 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard;
 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;
@@ -81,7 +81,7 @@ public class ProximityInfo {
 
     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 e3fea3dce1de22fe09dee0fc92a7b35557516b15..0aba813b2ac2ab9ae7d64fa1f381069ee326a248 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 android.text.TextUtils;
 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 @@ public class KeySpecParser {
         }
         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 @@ public class KeySpecParser {
             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 @@ public class KeySpecParser {
         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 @@ public class KeySpecParser {
         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/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 90ced60286cdfb34621f8aa776e00499e88b8544..31ff4e7b438c724f769be837cdfd05269a693a6b 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -104,7 +104,7 @@ public class BinaryDictionary extends Dictionary {
     }
 
     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 1607f86a86db720085b367e5faf9a583207e89d7..7a81f7bd59b8a6d9294c660eec2292275cdae1f6 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -164,7 +164,7 @@ public class DictionaryFactory {
         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 @@ public class DictionaryFactory {
         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 @@ public class DictionaryFactory {
     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 0000000000000000000000000000000000000000..4808b867a2c6262625d79d5f3d7532492ca5248b
--- /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 1858db949645a03b9daf2ee8dcc2b084d8cb89fe..c16a34faca9956c6cfa2458b081fb4a28a9f6dc9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -44,8 +44,8 @@ import android.view.HapticFeedbackConstants;
 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;
@@ -576,7 +576,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             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);
@@ -636,7 +636,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     /* 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);
     }
 
@@ -745,12 +745,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
                     + 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");
@@ -1207,7 +1207,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         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()) {
@@ -1247,7 +1247,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         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();
@@ -1263,7 +1264,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         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;
             }
@@ -1295,7 +1296,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         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)) {
@@ -1884,7 +1885,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
                 builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
             }
         }
-        if (Utils.shouldBlockAutoCorrectionBySafetyNet(builder, mSuggest)) {
+        if (Suggest.shouldBlockAutoCorrectionBySafetyNet(builder, mSuggest)) {
             builder.setShouldBlockAutoCorrectionBySafetyNet();
         }
         showSuggestions(builder.build(), typedWord);
@@ -2486,7 +2487,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
                 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 305cef22d130bb642f4bfafc1a345d99570fdc5e..72391f31e8a928b3ca80383a3e55f7c25eabf196 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -343,7 +343,7 @@ public class Settings extends InputMethodSettingsActivity
     @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 69e45f61959458816e3be901b408c169a13082d0..abd1dc6920cb3e4e74861ee718c31719e9001c7a 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -326,9 +326,9 @@ public class SettingsValues {
             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 0000000000000000000000000000000000000000..81c3b4edf811ae86a6e6095936488c7d30c7bdc5
--- /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 f5778167abfabe132280258b30846f887d90c170..ffbbf9bb8833a4100f4ac9f54c221b906c81d46a 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -512,7 +512,7 @@ public class SubtypeSwitcher {
     }
 
     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 0000000000000000000000000000000000000000..cb2bcf43f8023710e6d7bbe56238550967498453
--- /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 298ead665dbf8022284edfa6b5fd0eaaf004271e..671fb905d0a2b917ee0f9e3d4399c839a84f06ac 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -108,6 +108,8 @@ public class Suggest implements Dictionary.WordCallback {
     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);
     }
@@ -383,7 +385,7 @@ public class Suggest implements Dictionary.WordCallback {
         if (typedWord != null) {
             mSuggestions.add(0, typedWord.toString());
         }
-        Utils.removeDupes(mSuggestions);
+        StringUtils.removeDupes(mSuggestions);
 
         if (DBG) {
             double normalizedScore = mAutoCorrection.getNormalizedScore();
@@ -434,7 +436,7 @@ public class Suggest implements Dictionary.WordCallback {
         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) {
@@ -443,7 +445,7 @@ public class Suggest implements Dictionary.WordCallback {
                 // 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;
                 }
@@ -558,4 +560,46 @@ public class Suggest implements Dictionary.WordCallback {
         }
         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 a8679e07addd5a3c1410b389016ff3645d515378..f8dd5ae42d882330f1bccc86cf8106111c358ba3 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
 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.Environment;
 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.IOException;
 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 @@ public class Utils {
         }
     }
 
-    // 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 @@ public class Utils {
         }
     }
 
-    // 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 @@ public class Utils {
         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 @@ public class Utils {
             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 755c75b2e1a53d9a568eb213ecea1cbc9afa2e35..35a5c0f52813304cd54fd91db0f1c15a566ad4f3 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.DictionaryFactory;
 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.lang.ref.WeakReference;
 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService
                     }
                 }
                 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService
                 } 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService
         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);