Merge "Add defaults for manta" into jb-mr1-dev
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 76e76cc..27bf329 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -109,7 +109,7 @@
         <attr name="shadowRadius" format="float" />
         <attr name="backgroundDimAlpha" format="integer" />
 
-        <attr name="keyTextStyle" format="enum">
+        <attr name="keyTypeface" format="enum">
             <!-- This should be aligned with Typeface.NORMAL etc. -->
             <enum name="normal" value="0" />
             <enum name="bold" value="1" />
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 0220c83..a52ee06 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -42,7 +42,7 @@
         <item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
         <item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
         <item name="keyShiftedLetterHintRatio">@fraction/key_uppercase_letter_ratio</item>
-        <item name="keyTextStyle">normal</item>
+        <item name="keyTypeface">normal</item>
         <item name="keyTextColor">#FFFFFFFF</item>
         <item name="keyTextInactivatedColor">#FFFFFFFF</item>
         <item name="keyHintLetterColor">#80000000</item>
@@ -243,7 +243,7 @@
         name="KeyboardView.Stone.Bold"
         parent="KeyboardView.Stone"
     >
-        <item name="keyTextStyle">bold</item>
+        <item name="keyTypeface">bold</item>
     </style>
     <style
         name="MainKeyboardView.Stone.Bold"
@@ -272,7 +272,7 @@
     >
         <item name="android:background">@drawable/keyboard_dark_background</item>
         <item name="keyBackground">@drawable/btn_keyboard_key_gingerbread</item>
-        <item name="keyTextStyle">bold</item>
+        <item name="keyTypeface">bold</item>
     </style>
     <style
         name="MainKeyboardView.Gingerbread"
@@ -317,7 +317,7 @@
     >
         <item name="android:background">@drawable/keyboard_background_holo</item>
         <item name="keyBackground">@drawable/btn_keyboard_key_ics</item>
-        <item name="keyTextStyle">bold</item>
+        <item name="keyTypeface">bold</item>
         <item name="keyTextInactivatedColor">#66E0E4E5</item>
         <item name="keyHintLetterColor">#80000000</item>
         <item name="keyHintLabelColor">#A0FFFFFF</item>
diff --git a/java/res/xml-sw600dp-land/kbd_thai.xml b/java/res/xml-sw600dp-land/kbd_thai.xml
index b75980f..3143061 100644
--- a/java/res/xml-sw600dp-land/kbd_thai.xml
+++ b/java/res/xml-sw600dp-land/kbd_thai.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:rowHeight="20%p"
     latin:verticalGap="3.20%p"
+    latin:keyTypeface="normal"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/res/xml-sw600dp/kbd_thai.xml b/java/res/xml-sw600dp/kbd_thai.xml
index b75980f..3143061 100644
--- a/java/res/xml-sw600dp/kbd_thai.xml
+++ b/java/res/xml-sw600dp/kbd_thai.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:rowHeight="20%p"
     latin:verticalGap="3.20%p"
+    latin:keyTypeface="normal"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/res/xml-sw768dp-land/kbd_thai.xml b/java/res/xml-sw768dp-land/kbd_thai.xml
index b2cdbc3..b7633df 100644
--- a/java/res/xml-sw768dp-land/kbd_thai.xml
+++ b/java/res/xml-sw768dp-land/kbd_thai.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:rowHeight="20%p"
     latin:verticalGap="2.65%p"
+    latin:keyTypeface="normal"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/res/xml-sw768dp/kbd_thai.xml b/java/res/xml-sw768dp/kbd_thai.xml
index 593ccbd..2be6a25 100644
--- a/java/res/xml-sw768dp/kbd_thai.xml
+++ b/java/res/xml-sw768dp/kbd_thai.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:rowHeight="20%p"
     latin:verticalGap="2.95%p"
+    latin:keyTypeface="normal"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/res/xml/kbd_arabic.xml b/java/res/xml/kbd_arabic.xml
index ce5f30b..e3b90b0 100644
--- a/java/res/xml/kbd_arabic.xml
+++ b/java/res/xml/kbd_arabic.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyTypeface="normal"
 >
     <include
         latin:keyboardLayout="@xml/rows_arabic" />
diff --git a/java/res/xml/kbd_thai.xml b/java/res/xml/kbd_thai.xml
index 058ca16..b015d70 100644
--- a/java/res/xml/kbd_thai.xml
+++ b/java/res/xml/kbd_thai.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyTypeface="normal"
 >
     <include
         latin:keyboardLayout="@xml/rows_thai" />
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 58d3022..1eee1df 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -37,7 +37,7 @@
 import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.R;
 
-public class AccessibilityUtils {
+public final class AccessibilityUtils {
     private static final String TAG = AccessibilityUtils.class.getSimpleName();
     private static final String CLASS = AccessibilityUtils.class.getClass().getName();
     private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage()
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index ce427e9..ffed6ec 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -24,7 +24,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
-public class CompatUtils {
+public final class CompatUtils {
     private static final String TAG = CompatUtils.class.getSimpleName();
     private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
     // TODO: Can these be constants instead of literal String constants?
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index 08c246f..210058b 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -20,7 +20,7 @@
 
 import java.lang.reflect.Field;
 
-public class EditorInfoCompatUtils {
+public final class EditorInfoCompatUtils {
     // EditorInfo.IME_FLAG_FORCE_ASCII has been introduced since API#16 (JellyBean).
     private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(
             EditorInfo.class, "IME_FLAG_FORCE_ASCII");
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
index 0befa7a..8eea31e 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
@@ -20,7 +20,7 @@
 
 import java.lang.reflect.Method;
 
-public class InputMethodServiceCompatUtils {
+public final class InputMethodServiceCompatUtils {
     private static final Method METHOD_enableHardwareAcceleration =
             CompatUtils.getMethod(InputMethodService.class, "enableHardwareAcceleration");
 
diff --git a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
index 1b79992..db5abd0 100644
--- a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
@@ -18,7 +18,7 @@
 
 import java.lang.reflect.Field;
 
-public class SettingsSecureCompatUtils {
+public final class SettingsSecureCompatUtils {
     private static final Field FIELD_ACCESSIBILITY_SPEAK_PASSWORD = CompatUtils.getField(
             android.provider.Settings.Secure.class, "ACCESSIBILITY_SPEAK_PASSWORD");
 
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 6ba309f..159f436 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -33,7 +33,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-public class SuggestionSpanUtils {
+public final class SuggestionSpanUtils {
     private static final String TAG = SuggestionSpanUtils.class.getSimpleName();
     // TODO: Use reflection to get field values
     public static final String ACTION_SUGGESTION_PICKED =
diff --git a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
index e5f9db2..8314212 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
@@ -20,7 +20,7 @@
 
 import java.lang.reflect.Field;
 
-public class SuggestionsInfoCompatUtils {
+public final class SuggestionsInfoCompatUtils {
     private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = CompatUtils.getField(
             SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS");
     private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = (Integer) CompatUtils
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index e37868b..a5f9e9e 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.graphics.Typeface;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -37,8 +38,8 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -120,6 +121,9 @@
     /** Default gap between rows */
     public final int mVerticalGap;
 
+    /** Per keyboard key visual parameters */
+    public final Typeface mKeyTypeface;
+
     public final int mMostCommonKeyHeight;
     public final int mMostCommonKeyWidth;
 
@@ -150,6 +154,8 @@
         mMoreKeysTemplate = params.mMoreKeysTemplate;
         mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
 
+        mKeyTypeface = params.mKeyTypeface;
+
         mTopPadding = params.mTopPadding;
         mVerticalGap = params.mVerticalGap;
 
@@ -225,6 +231,7 @@
         return mId.toString();
     }
 
+    // TODO: Move this class to internal package
     public static class Params {
         public KeyboardId mId;
         public int mThemeId;
@@ -244,6 +251,8 @@
         public int mHorizontalEdgesPadding;
         public int mHorizontalCenterPadding;
 
+        public Typeface mKeyTypeface = null;
+
         public int mDefaultRowHeight;
         public int mDefaultKeyWidth;
         public int mHorizontalGap;
@@ -497,6 +506,7 @@
      * </pre>
      */
 
+    // TODO: Move this class to internal package.
     public static class Builder<KP extends Params> {
         private static final String BUILDER_TAG = "Keyboard.Builder";
         private static final boolean DEBUG = false;
@@ -744,9 +754,11 @@
                     R.style.Keyboard);
             final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                     R.styleable.Keyboard_Key);
+            final TypedArray keyboardViewAttr = mResources.obtainAttributes(
+                    Xml.asAttributeSet(parser), R.styleable.KeyboardView);
             try {
                 final int displayHeight = mDisplayMetrics.heightPixels;
-                final String keyboardHeightString = Utils.getDeviceOverrideValue(
+                final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue(
                         mResources, R.array.keyboard_heights, null);
                 final float keyboardHeight;
                 if (keyboardHeightString != null) {
@@ -795,6 +807,11 @@
                         R.styleable.Keyboard_rowHeight, params.mBaseHeight,
                         params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
 
+                if (keyboardViewAttr.hasValue(R.styleable.KeyboardView_keyTypeface)) {
+                    params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt(
+                            R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
+                }
+
                 params.mMoreKeysTemplate = keyboardAttr.getResourceId(
                         R.styleable.Keyboard_moreKeysTemplate, 0);
                 params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
@@ -825,6 +842,7 @@
                     params.mTouchPositionCorrection.load(data);
                 }
             } finally {
+                keyboardViewAttr.recycle();
                 keyAttr.recycle();
                 keyboardAttr.recycle();
             }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 127ac71..a479709 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -65,7 +65,7 @@
  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
- * @attr ref R.styleable#KeyboardView_keyTextStyle
+ * @attr ref R.styleable#KeyboardView_keyTypeface
  * @attr ref R.styleable#KeyboardView_keyPreviewLayout
  * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
  * @attr ref R.styleable#KeyboardView_keyPreviewOffset
@@ -180,11 +180,11 @@
         }
     }
 
+    // Move this class to internal package
     protected static class KeyDrawParams {
         // XML attributes
         public final int mKeyTextColor;
         public final int mKeyTextInactivatedColor;
-        public final Typeface mKeyTextStyle;
         public final float mKeyLabelHorizontalPadding;
         public final float mKeyHintLetterPadding;
         public final float mKeyPopupHintLetterPadding;
@@ -197,7 +197,8 @@
         public final int mKeyShiftedLetterHintInactivatedColor;
         public final int mKeyShiftedLetterHintActivatedColor;
 
-        /* package */ final float mKeyLetterRatio;
+        private final Typeface mKeyTypefaceFromKeyboardView;
+        private final float mKeyLetterRatio;
         private final float mKeyLargeLetterRatio;
         private final float mKeyLabelRatio;
         private final float mKeyLargeLabelRatio;
@@ -206,6 +207,7 @@
         private final float mKeyHintLabelRatio;
 
         public final Rect mPadding = new Rect();
+        public Typeface mKeyTypeface;
         public int mKeyLetterSize;
         public int mKeyLargeLetterSize;
         public int mKeyLabelSize;
@@ -248,15 +250,19 @@
                     R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
             mKeyShiftedLetterHintActivatedColor = a.getColor(
                     R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
-            mKeyTextStyle = Typeface.defaultFromStyle(
-                    a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
+            mKeyTypefaceFromKeyboardView = Typeface.defaultFromStyle(
+                    a.getInt(R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
+            mKeyTypeface = mKeyTypefaceFromKeyboardView;
             mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
             mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
 
             mKeyBackground.getPadding(mPadding);
         }
 
-        public void updateKeyHeight(int keyHeight) {
+        public void updateParams(final Keyboard keyboard) {
+            mKeyTypeface = (keyboard.mKeyTypeface != null)
+                    ? keyboard.mKeyTypeface : mKeyTypefaceFromKeyboardView;
+            final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
             if (isValidFraction(mKeyLetterRatio)) {
                 mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
             }
@@ -270,13 +276,14 @@
             mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
         }
 
-        public void blendAlpha(Paint paint) {
+        public void blendAlpha(final Paint paint) {
             final int color = paint.getColor();
             paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
                     Color.red(color), Color.green(color), Color.blue(color));
         }
     }
 
+    // TODO: Move this class to internal package.
     /* package */ static class KeyPreviewDrawParams {
         // XML attributes.
         public final Drawable mPreviewBackground;
@@ -285,11 +292,9 @@
         public final int mPreviewTextColor;
         public final int mPreviewOffset;
         public final int mPreviewHeight;
-        public final Typeface mKeyTextStyle;
         public final int mLingerTimeout;
 
         private final float mPreviewTextRatio;
-        private final float mKeyLetterRatio;
 
         // The graphical geometry of the key preview.
         // <-width->
@@ -316,13 +321,14 @@
         // preview background.
         public int mPreviewVisibleOffset;
 
+        public Typeface mKeyTypeface;
         public int mPreviewTextSize;
         public int mKeyLetterSize;
         public final int[] mCoordinates = new int[2];
 
         private static final int PREVIEW_ALPHA = 240;
 
-        public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) {
+        public KeyPreviewDrawParams(final TypedArray a) {
             mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground);
             mPreviewLeftBackground = a.getDrawable(
                     R.styleable.KeyboardView_keyPreviewLeftBackground);
@@ -338,21 +344,18 @@
             mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
             mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
             mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
-
-            mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
-            mKeyTextStyle = keyDrawParams.mKeyTextStyle;
         }
 
-        public void updateKeyHeight(int keyHeight) {
-            if (mPreviewTextRatio >= 0.0f) {
+        public void updateParams(final Keyboard keyboard, final KeyDrawParams keyDrawParams) {
+            final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
+            if (isValidFraction(mPreviewTextRatio)) {
                 mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
             }
-            if (mKeyLetterRatio >= 0.0f) {
-                mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
-            }
+            mKeyLetterSize = keyDrawParams.mKeyLetterSize;
+            mKeyTypeface = keyDrawParams.mKeyTypeface;
         }
 
-        private static void setAlpha(Drawable drawable, int alpha) {
+        private static void setAlpha(final Drawable drawable, final int alpha) {
             if (drawable == null) return;
             drawable.setAlpha(alpha);
         }
@@ -368,7 +371,7 @@
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
         mKeyDrawParams = new KeyDrawParams(a);
-        mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
+        mKeyPreviewDrawParams = new KeyPreviewDrawParams(a);
         mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
         mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
         if (mKeyPreviewLayoutId == 0) {
@@ -416,9 +419,8 @@
         LatinImeLogger.onSetKeyboard(keyboard);
         requestLayout();
         invalidateAllKeys();
-        final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
-        mKeyDrawParams.updateKeyHeight(keyHeight);
-        mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
+        mKeyDrawParams.updateParams(keyboard);
+        mKeyPreviewDrawParams.updateParams(keyboard, mKeyDrawParams);
     }
 
     /**
@@ -648,7 +650,7 @@
         if (key.mLabel != null) {
             final String label = key.mLabel;
             // For characters, use large font. For labels like "Done", use smaller font.
-            paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
+            paint.setTypeface(key.selectTypeface(params.mKeyTypeface));
             final int labelSize = key.selectTextSize(params.mKeyLetterSize,
                     params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyLargeLabelSize,
                     params.mKeyHintLabelSize);
@@ -812,7 +814,7 @@
         final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
         final int keyHeight = key.mHeight;
 
-        paint.setTypeface(params.mKeyTextStyle);
+        paint.setTypeface(params.mKeyTypeface);
         paint.setTextSize(params.mKeyHintLetterSize);
         paint.setColor(params.mKeyHintLabelColor);
         paint.setTextAlign(Align.CENTER);
@@ -911,7 +913,7 @@
     public Paint newDefaultLabelPaint() {
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
-        paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
+        paint.setTypeface(mKeyDrawParams.mKeyTypeface);
         paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
         return paint;
     }
@@ -1019,7 +1021,7 @@
 
         final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
         final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
-        // What we show as preview should match what we show on a key top in onBufferDraw().
+        // What we show as preview should match what we show on a key top in onDraw().
         if (label != null) {
             // TODO Should take care of temporaryShiftLabel here.
             previewText.setCompoundDrawables(null, null, null, null);
@@ -1028,7 +1030,7 @@
                 previewText.setTypeface(Typeface.DEFAULT_BOLD);
             } else {
                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
-                previewText.setTypeface(params.mKeyTextStyle);
+                previewText.setTypeface(params.mKeyTypeface);
             }
             previewText.setText(label);
         } else {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index df84271..358061b 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -48,10 +48,10 @@
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
@@ -334,7 +334,7 @@
                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
         final Resources res = getResources();
         final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
-                Utils.getDeviceOverrideValue(res,
+               ResourceUtils.getDeviceOverrideValue(res,
                         R.array.phantom_sudden_move_event_device_list, "false"));
         PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack);
 
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
index ee50470..dc12fa4 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
@@ -22,7 +22,7 @@
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
-public class ViewLayoutUtils {
+public final class ViewLayoutUtils {
     private ViewLayoutUtils() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
index 9e2cbec..a591a7a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
@@ -24,7 +24,7 @@
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
@@ -53,7 +53,7 @@
 
     public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
         mView = view;
-        mNeedsSuddenJumpingHack = Boolean.parseBoolean(Utils.getDeviceOverrideValue(
+        mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
                 context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false"));
     }
 
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java
index baa2ee1..c75f2df 100644
--- a/java/src/com/android/inputmethod/latin/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/CollectionUtils.java
@@ -30,7 +30,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-public class CollectionUtils {
+public final class CollectionUtils {
     private CollectionUtils() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java
index 1461c02..2674e45 100644
--- a/java/src/com/android/inputmethod/latin/ImfUtils.java
+++ b/java/src/com/android/inputmethod/latin/ImfUtils.java
@@ -29,7 +29,7 @@
 /**
  * Utility class for Input Method Framework
  */
-public class ImfUtils {
+public final class ImfUtils {
     private ImfUtils() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 40c3b76..500866a 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -18,7 +18,7 @@
 
 import android.text.InputType;
 
-public class InputTypeUtils implements InputType {
+public final class InputTypeUtils implements InputType {
     private static final int WEB_TEXT_PASSWORD_INPUT_TYPE =
             TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD;
     private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE =
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
index 86a3826..f930599 100644
--- a/java/src/com/android/inputmethod/latin/JniUtils.java
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -20,7 +20,7 @@
 
 import com.android.inputmethod.latin.define.JniLibName;
 
-public class JniUtils {
+public final class JniUtils {
     private static final String TAG = JniUtils.class.getSimpleName();
 
     private JniUtils() {
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index bb39ce4..dd73a97 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -38,12 +38,12 @@
     // an auto-correction.
     public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3;
 
-    public static final int NOT_A_SEPARATOR = -1;
+    public static final String NOT_A_SEPARATOR = "";
 
     public final int[] mPrimaryKeyCodes;
     public final String mTypedWord;
     public final String mCommittedWord;
-    public final int mSeparatorCode;
+    public final String mSeparatorString;
     public final CharSequence mPrevWord;
     public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH);
 
@@ -56,14 +56,14 @@
     // immutable. Do not fiddle with their contents after you passed them to this constructor.
     public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
             final String typedWord, final String committedWord,
-            final int separatorCode, final CharSequence prevWord) {
+            final String separatorString, final CharSequence prevWord) {
         mPrimaryKeyCodes = primaryKeyCodes;
         if (inputPointers != null) {
             mInputPointers.copy(inputPointers);
         }
         mTypedWord = typedWord;
         mCommittedWord = committedWord;
-        mSeparatorCode = separatorCode;
+        mSeparatorString = separatorString;
         mActive = true;
         mPrevWord = prevWord;
     }
@@ -80,7 +80,7 @@
         return TextUtils.equals(mTypedWord, mCommittedWord);
     }
 
-    public static int getSeparatorLength(final int separatorCode) {
-        return NOT_A_SEPARATOR == separatorCode ? 0 : 1;
+    public static int getSeparatorLength(final String separatorString) {
+        return StringUtils.codePointCount(separatorString);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 83a3068..76f4957 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1055,7 +1055,7 @@
             mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     }
 
-    private void commitTyped(final int separatorCode) {
+    private void commitTyped(final String separatorString) {
         if (!mWordComposer.isComposingWord()) return;
         final CharSequence typedWord = mWordComposer.getTypedWord();
         if (typedWord.length() > 0) {
@@ -1063,7 +1063,7 @@
             final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
             mLastComposedWord = mWordComposer.commitWord(
                     LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
-                    separatorCode, prevWord);
+                    separatorString, prevWord);
         }
     }
 
@@ -1340,7 +1340,9 @@
         if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT
                 && primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL)
             mLastComposedWord.deactivate();
-        mEnteredText = null;
+        if (Keyboard.CODE_DELETE != primaryCode) {
+            mEnteredText = null;
+        }
         mConnection.endBatchEdit();
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
@@ -1352,7 +1354,9 @@
     public void onTextInput(CharSequence rawText) {
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
-            commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
+            commitCurrentAutoCorrection(rawText.toString());
+        } else {
+            resetComposingState(true /* alsoResetLastComposedWord */);
         }
         mHandler.postUpdateSuggestionStrip();
         final CharSequence text = specificTldProcessingOnTextInput(rawText);
@@ -1365,7 +1369,6 @@
         mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
         mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
-        resetComposingState(true /* alsoResetLastComposedWord */);
     }
 
     @Override
@@ -1451,18 +1454,6 @@
         // In many cases, we may have to put the keyboard in auto-shift state again.
         mHandler.postUpdateShiftState();
 
-        if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
-            // Cancel multi-character input: remove the text we just entered.
-            // This is triggered on backspace after a key that inputs multiple characters,
-            // like the smiley key or the .com key.
-            final int length = mEnteredText.length();
-            mConnection.deleteSurroundingText(length, 0);
-            // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
-            // In addition we know that spaceState is false, and that we should not be
-            // reverting any autocorrect at this point. So we can safely return.
-            return;
-        }
-
         if (mWordComposer.isComposingWord()) {
             final int length = mWordComposer.size();
             if (length > 0) {
@@ -1483,6 +1474,18 @@
                 revertCommit();
                 return;
             }
+            if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
+                // Cancel multi-character input: remove the text we just entered.
+                // This is triggered on backspace after a key that inputs multiple characters,
+                // like the smiley key or the .com key.
+                final int length = mEnteredText.length();
+                mConnection.deleteSurroundingText(length, 0);
+                mEnteredText = null;
+                // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
+                // In addition we know that spaceState is false, and that we should not be
+                // reverting any autocorrect at this point. So we can safely return.
+                return;
+            }
             if (SPACE_STATE_DOUBLE == spaceState) {
                 mHandler.cancelDoubleSpacesTimer();
                 if (mConnection.revertDoubleSpace()) {
@@ -1626,10 +1629,11 @@
         // Handle separator
         if (mWordComposer.isComposingWord()) {
             if (mCurrentSettings.mCorrectionEnabled) {
-                commitCurrentAutoCorrection(primaryCode);
+                // TODO: maybe cache Strings in an <String> sparse array or something
+                commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
                 didAutoCorrect = true;
             } else {
-                commitTyped(primaryCode);
+                commitTyped(new String(new int[]{primaryCode}, 0, 1));
             }
         }
 
@@ -1834,7 +1838,7 @@
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
 
-    private void commitCurrentAutoCorrection(final int separatorCodePoint) {
+    private void commitCurrentAutoCorrection(final String separatorString) {
         // Complete any pending suggestions query first
         if (mHandler.hasPendingUpdateSuggestions()) {
             updateSuggestionStrip();
@@ -1848,10 +1852,10 @@
                 throw new RuntimeException("We have an auto-correction but the typed word "
                         + "is empty? Impossible! I must commit suicide.");
             }
-            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
+            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorString);
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
-                    separatorCodePoint);
+                    separatorString);
             if (!typedWord.equals(autoCorrection)) {
                 // This will make the correction flash for a short while as a visual clue
                 // to the user that auto-correction happened.
@@ -1949,7 +1953,7 @@
      * Commits the chosen word to the text field and saves it for later retrieval.
      */
     private void commitChosenWord(final CharSequence chosenWord, final int commitType,
-            final int separatorCode) {
+            final String separatorString) {
         final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
         mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
                 this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
@@ -1960,7 +1964,7 @@
         // LastComposedWord#didCommitTypedWord by string equality of the remembered
         // strings.
         mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord.toString(),
-                separatorCode, prevWord);
+                separatorString, prevWord);
     }
 
     private void setPunctuationSuggestions() {
@@ -2030,7 +2034,7 @@
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
         final int cancelLength = committedWord.length();
         final int separatorLength = LastComposedWord.getSeparatorLength(
-                mLastComposedWord.mSeparatorCode);
+                mLastComposedWord.mSeparatorString);
         // TODO: should we check our saved separator against the actual contents of the text view?
         final int deleteLength = cancelLength + separatorLength;
         if (DEBUG) {
@@ -2051,10 +2055,8 @@
             mUserHistoryDictionary.cancelAddingUserHistory(
                     previousWord.toString(), committedWord.toString());
         }
-        mConnection.commitText(originallyTypedWord, 1);
-        // Re-insert the separator
-        sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
-        Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
+        mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
+        Utils.Stats.onSeparator(mLastComposedWord.mSeparatorString,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_revertCommit(originallyTypedWord);
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index 3b08cab..feb1b2d 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -31,7 +31,10 @@
  * update/bugfix to this file, consider also updating/fixing the version in the
  * dictionary pack.
  */
-public class LocaleUtils {
+public final class LocaleUtils {
+    private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
+    private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
+
     private LocaleUtils() {
         // Intentional empty constructor for utility class.
     }
@@ -219,4 +222,38 @@
             return retval;
         }
     }
+
+    public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return EMPTY_LT_HASH_MAP;
+        }
+        final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
+        final int N = ss.length;
+        if (N < 2 || N % 2 != 0) {
+            return EMPTY_LT_HASH_MAP;
+        }
+        final HashMap<String, Long> retval = CollectionUtils.newHashMap();
+        for (int i = 0; i < N / 2; ++i) {
+            final String localeStr = ss[i * 2];
+            final long time = Long.valueOf(ss[i * 2 + 1]);
+            retval.put(localeStr, time);
+        }
+        return retval;
+    }
+
+    public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
+        if (map == null || map.isEmpty()) {
+            return "";
+        }
+        final StringBuilder builder = new StringBuilder();
+        for (String localeStr : map.keySet()) {
+            if (builder.length() > 0) {
+                builder.append(LOCALE_AND_TIME_STR_SEPARATER);
+            }
+            final Long time = map.get(localeStr);
+            builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
+            builder.append(String.valueOf(time));
+        }
+        return builder.toString();
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
new file mode 100644
index 0000000..e01ac3d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -0,0 +1,48 @@
+/*
+ * 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.res.Resources;
+import android.os.Build;
+
+import java.util.HashMap;
+
+public final class ResourceUtils {
+    private ResourceUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
+    private static final HashMap<String, String> sDeviceOverrideValueMap =
+            CollectionUtils.newHashMap();
+
+    public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
+        final int orientation = res.getConfiguration().orientation;
+        final String key = overrideResId + "-" + orientation;
+        if (!sDeviceOverrideValueMap.containsKey(key)) {
+            String overrideValue = defValue;
+            for (final String element : res.getStringArray(overrideResId)) {
+                if (element.startsWith(HARDWARE_PREFIX)) {
+                    overrideValue = element.substring(HARDWARE_PREFIX.length());
+                    break;
+                }
+            }
+            sDeviceOverrideValueMap.put(key, overrideValue);
+        }
+        return sDeviceOverrideValueMap.get(key);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index dcd2532..5e355a3 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -375,8 +375,8 @@
             return volume;
         }
 
-        return Float.parseFloat(
-                Utils.getDeviceOverrideValue(res, R.array.keypress_volumes, "-1.0f"));
+        return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(
+                res, R.array.keypress_volumes, "-1.0f"));
     }
 
     // Likewise
@@ -388,8 +388,8 @@
             return ms;
         }
 
-        return Integer.parseInt(
-                Utils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations, "-1"));
+        return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(
+                res, R.array.keypress_vibration_durations, "-1"));
     }
 
     // Likewise
@@ -401,7 +401,7 @@
     public static long getLastUserHistoryWriteTime(
             final SharedPreferences prefs, final String locale) {
         final String str = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
-        final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(str);
+        final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
         if (map.containsKey(locale)) {
             return map.get(locale);
         }
@@ -411,9 +411,9 @@
     public static void setLastUserHistoryWriteTime(
             final SharedPreferences prefs, final String locale) {
         final String oldStr = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
-        final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(oldStr);
+        final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
         map.put(locale, System.currentTimeMillis());
-        final String newStr = Utils.localeAndTimeHashMapToStr(map);
+        final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
         prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 39c59b4..9c47a38 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -21,7 +21,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-public class StringUtils {
+public final class StringUtils {
     private StringUtils() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 5a2fdf4..3d3bd98 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -19,7 +19,7 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 
-public class UserHistoryForgettingCurveUtils {
+public final class UserHistoryForgettingCurveUtils {
     private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static final int FC_FREQ_MAX = 127;
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index fc7a421..1c98b92 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,20 +16,16 @@
 
 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;
-import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -45,9 +41,8 @@
 import java.nio.channels.FileChannel;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.HashMap;
 
-public class Utils {
+public final class Utils {
     private Utils() {
         // This utility class is not publicly instantiable.
     }
@@ -184,7 +179,7 @@
         return getStackTrace(Integer.MAX_VALUE - 1);
     }
 
-    public static class UsabilityStudyLogUtils {
+    public static final class UsabilityStudyLogUtils {
         // TODO: remove code duplication with ResearchLog class
         private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
         private static final String FILENAME = "log.txt";
@@ -393,34 +388,38 @@
         }
     }
 
-    public static float getDipScale(Context context) {
-        final float scale = context.getResources().getDisplayMetrics().density;
-        return scale;
-    }
-
-    /** Convert pixel to DIP */
-    public static int dipToPixel(float scale, int dip) {
-        return (int) (dip * scale + 0.5);
-    }
-
-    public static class Stats {
+    public static final class Stats {
         public static void onNonSeparator(final char code, final int x,
                 final int y) {
             RingCharBuffer.getInstance().push(code, x, y);
             LatinImeLogger.logOnInputChar();
         }
 
-        public static void onSeparator(final int code, final int x,
-                final int y) {
-            // TODO: accept code points
-            RingCharBuffer.getInstance().push((char)code, x, y);
+        public static void onSeparator(final int code, final int x, final int y) {
+            // Helper method to log a single code point separator
+            // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+            onSeparator(new String(new int[]{code}, 0, 1), x, y);
+        }
+
+        public static void onSeparator(final String separator, final int x, final int y) {
+            final int length = separator.length();
+            for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+                int codePoint = Character.codePointAt(separator, i);
+                // TODO: accept code points
+                RingCharBuffer.getInstance().push((char)codePoint, x, y);
+            }
             LatinImeLogger.logOnInputSeparator();
         }
 
         public static void onAutoCorrection(final String typedWord, final String correctedWord,
-                final int separatorCode) {
+                final String separatorString) {
             if (TextUtils.isEmpty(typedWord)) return;
-            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
+            // TODO: this fails when the separator is more than 1 code point long, but
+            // the backend can't handle it yet. The only case when this happens is with
+            // smileys and other multi-character keys.
+            final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+                    : separatorString.codePointAt(0);
+            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, codePoint);
         }
 
         public static void onAutoCorrectionCancellation() {
@@ -436,60 +435,4 @@
         if (TextUtils.isEmpty(info)) return null;
         return info;
     }
-
-    private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
-    private static final HashMap<String, String> sDeviceOverrideValueMap =
-            CollectionUtils.newHashMap();
-
-    public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
-        final int orientation = res.getConfiguration().orientation;
-        final String key = overrideResId + "-" + orientation;
-        if (!sDeviceOverrideValueMap.containsKey(key)) {
-            String overrideValue = defValue;
-            for (final String element : res.getStringArray(overrideResId)) {
-                if (element.startsWith(HARDWARE_PREFIX)) {
-                    overrideValue = element.substring(HARDWARE_PREFIX.length());
-                    break;
-                }
-            }
-            sDeviceOverrideValueMap.put(key, overrideValue);
-        }
-        return sDeviceOverrideValueMap.get(key);
-    }
-
-    private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
-    private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
-    public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
-        if (TextUtils.isEmpty(str)) {
-            return EMPTY_LT_HASH_MAP;
-        }
-        final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
-        final int N = ss.length;
-        if (N < 2 || N % 2 != 0) {
-            return EMPTY_LT_HASH_MAP;
-        }
-        final HashMap<String, Long> retval = CollectionUtils.newHashMap();
-        for (int i = 0; i < N / 2; ++i) {
-            final String localeStr = ss[i * 2];
-            final long time = Long.valueOf(ss[i * 2 + 1]);
-            retval.put(localeStr, time);
-        }
-        return retval;
-    }
-
-    public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
-        if (map == null || map.isEmpty()) {
-            return "";
-        }
-        final StringBuilder builder = new StringBuilder();
-        for (String localeStr : map.keySet()) {
-            if (builder.length() > 0) {
-                builder.append(LOCALE_AND_TIME_STR_SEPARATER);
-            }
-            final Long time = map.get(localeStr);
-            builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
-            builder.append(String.valueOf(time));
-        }
-        return builder.toString();
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/VibratorUtils.java b/java/src/com/android/inputmethod/latin/VibratorUtils.java
index 33ffdd9..b6696ce 100644
--- a/java/src/com/android/inputmethod/latin/VibratorUtils.java
+++ b/java/src/com/android/inputmethod/latin/VibratorUtils.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.os.Vibrator;
 
-public class VibratorUtils {
+public final class VibratorUtils {
     private static final VibratorUtils sInstance = new VibratorUtils();
     private Vibrator mVibrator;
 
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index ecec60f..4b7adf2 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -336,14 +336,14 @@
 
     // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
     public LastComposedWord commitWord(final int type, final String committedWord,
-            final int separatorCode, final CharSequence prevWord) {
+            final String separatorString, final CharSequence prevWord) {
         // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
         // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
         // the last composed word to ensure this does not happen.
         final int[] primaryKeyCodes = mPrimaryKeyCodes;
         mPrimaryKeyCodes = new int[N];
         final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
-                mInputPointers, mTypedWord.toString(), committedWord, separatorCode,
+                mInputPointers, mTypedWord.toString(), committedWord, separatorString,
                 prevWord);
         mInputPointers.reset();
         if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index 481cdfa..b5cbaf1 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -23,7 +23,7 @@
 
 import java.io.IOException;
 
-public class XmlParseUtils {
+public final class XmlParseUtils {
     private XmlParseUtils() {
         // This utility class is not publicly instantiable.
     }
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java b/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
index 07a6c30..6d6bc0e 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
+++ b/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
@@ -26,7 +26,7 @@
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
-public class JarUtils {
+public final class JarUtils {
     private JarUtils() {
         // This utility class is not publicly instantiable.
     }