Merge "Allow Latin IME to cancel smiley-auto-correct consistenly" into jb-mr1-dev
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index cced45d..76e76cc 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -41,26 +41,24 @@
              checkable+checked+pressed. -->
         <attr name="keyBackground" format="reference" />
 
-        <!-- Size of the text for one letter keys. If not defined, keyLetterRatio takes effect. -->
-        <attr name="keyLetterSize" format="dimension" />
-        <!-- Size of the text for keys with multiple letters. If not defined, keyLabelRatio takes
-             effect. -->
-        <attr name="keyLabelSize" format="dimension" />
-        <!-- Size of the text for one letter keys, in the proportion of key height. -->
-        <attr name="keyLetterRatio" format="float" />
+        <!-- Size of the text for one letter keys. If specified as fraction, the text size is
+             measured in the proportion of key height. -->
+        <attr name="keyLetterSize" format="dimension|fraction" />
+        <!-- Size of the text for keys with multiple letters. If specified as fraction, the text
+             size is measured in the proportion of key height. -->
+        <attr name="keyLabelSize" format="dimension|fraction" />
         <!-- Large size of the text for one letter keys, in the proportion of key height. -->
-        <attr name="keyLargeLetterRatio" format="float" />
-        <!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
-        <attr name="keyLabelRatio" format="float" />
+        <attr name="keyLargeLetterRatio" format="fraction" />
         <!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
-        <attr name="keyLargeLabelRatio" format="float" />
+        <attr name="keyLargeLabelRatio" format="fraction" />
         <!-- Size of the text for hint letter (= one character hint label), in the proportion of
              key height. -->
-        <attr name="keyHintLetterRatio" format="float" />
+        <attr name="keyHintLetterRatio" format="fraction" />
         <!-- Size of the text for hint label, in the proportion of key height. -->
-        <attr name="keyHintLabelRatio" format="float" />
+        <attr name="keyHintLabelRatio" format="fraction" />
         <!-- Size of the text for shifted letter hint, in the proportion of key height. -->
-        <attr name="keyShiftedLetterHintRatio" format="float" />
+        <attr name="keyShiftedLetterHintRatio" format="dimension|fraction" />
+
         <!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
         <attr name="keyLabelHorizontalPadding" format="dimension" />
         <!-- Right padding of hint letter to the edge of the key.-->
@@ -96,8 +94,8 @@
         <attr name="keyPreviewOffset" format="dimension" />
         <!-- Height of the key press feedback popup. -->
         <attr name="keyPreviewHeight" format="dimension" />
-        <!-- Size of the text for key press feedback popup, int the proportion of key height -->
-        <attr name="keyPreviewTextRatio" format="float" />
+        <!-- Size of the text for key press feedback popup, in the proportion of key height. -->
+        <attr name="keyPreviewTextRatio" format="fraction" />
         <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
         <attr name="keyPreviewLingerTimeout" format="integer" />
 
@@ -187,13 +185,13 @@
         <attr name="colorTypedWord" format="color" />
         <attr name="colorAutoCorrect" format="color" />
         <attr name="colorSuggested" format="color" />
-        <attr name="alphaValidTypedWord" format="integer" />
-        <attr name="alphaTypedWord" format="integer" />
-        <attr name="alphaAutoCorrect" format="integer" />
-        <attr name="alphaSuggested" format="integer" />
-        <attr name="alphaObsoleted" format="integer" />
+        <attr name="alphaValidTypedWord" format="fraction" />
+        <attr name="alphaTypedWord" format="fraction" />
+        <attr name="alphaAutoCorrect" format="fraction" />
+        <attr name="alphaSuggested" format="fraction" />
+        <attr name="alphaObsoleted" format="fraction" />
         <attr name="suggestionsCountInStrip" format="integer" />
-        <attr name="centerSuggestionPercentile" format="integer" />
+        <attr name="centerSuggestionPercentile" format="fraction" />
         <attr name="maxMoreSuggestionsRow" format="integer" />
         <attr name="minMoreSuggestionsWidth" format="float" />
     </declare-styleable>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index c59bad3..4fd942b 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -92,7 +92,7 @@
     <dimen name="suggestion_text_size">18dp</dimen>
     <dimen name="more_suggestions_hint_text_size">27dp</dimen>
     <integer name="suggestions_count_in_strip">3</integer>
-    <integer name="center_suggestion_percentile">36</integer>
+    <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview parameters -->
     <dimen name="gesture_preview_trail_width">2.5dp</dimen>
@@ -101,4 +101,7 @@
     <dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
     <dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
     <dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
+
+    <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
+    <dimen name="accessibility_edge_slop">8dp</dimen>
 </resources>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index e5e7fed..0220c83 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -35,9 +35,9 @@
     <style name="KeyboardView">
         <item name="android:background">@drawable/keyboard_background</item>
         <item name="keyBackground">@drawable/btn_keyboard_key</item>
-        <item name="keyLetterRatio">@fraction/key_letter_ratio</item>
+        <item name="keyLetterSize">@fraction/key_letter_ratio</item>
         <item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
-        <item name="keyLabelRatio">@fraction/key_label_ratio</item>
+        <item name="keyLabelSize">@fraction/key_label_ratio</item>
         <item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
         <item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
         <item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
@@ -138,9 +138,9 @@
         <item name="colorTypedWord">@android:color/white</item>
         <item name="colorAutoCorrect">#FFFCAE00</item>
         <item name="colorSuggested">#FFFCAE00</item>
-        <item name="alphaObsoleted">50</item>
+        <item name="alphaObsoleted">50%</item>
         <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
-        <item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
+        <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
         <item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
         <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
     </style>
@@ -373,12 +373,12 @@
         <item name="colorTypedWord">@android:color/holo_blue_light</item>
         <item name="colorAutoCorrect">@android:color/holo_blue_light</item>
         <item name="colorSuggested">@android:color/holo_blue_light</item>
-        <item name="alphaValidTypedWord">85</item>
-        <item name="alphaTypedWord">85</item>
-        <item name="alphaSuggested">70</item>
-        <item name="alphaObsoleted">70</item>
+        <item name="alphaValidTypedWord">85%</item>
+        <item name="alphaTypedWord">85%</item>
+        <item name="alphaSuggested">70%</item>
+        <item name="alphaObsoleted">70%</item>
         <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
-        <item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
+        <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
         <item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
         <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
     </style>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 2fff731..e42de0b 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -24,7 +24,6 @@
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -44,7 +43,7 @@
 
     /**
      * Inset in pixels to look for keys when the user's finger exits the
-     * keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
+     * keyboard area.
      */
     private int mEdgeSlop;
 
@@ -62,7 +61,8 @@
 
     private void initInternal(InputMethodService inputMethod) {
         mInputMethod = inputMethod;
-        mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
+        mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
+                R.dimen.accessibility_edge_slop);
     }
 
     /**
@@ -114,8 +114,14 @@
     public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
         final int x = (int) event.getX();
         final int y = (int) event.getY();
-        final Key key = tracker.getKeyOn(x, y);
         final Key previousKey = mLastHoverKey;
+        final Key key;
+
+        if (pointInView(x, y)) {
+            key = tracker.getKeyOn(x, y);
+        } else {
+            key = null;
+        }
 
         mLastHoverKey = key;
 
@@ -123,7 +129,7 @@
         case MotionEvent.ACTION_HOVER_EXIT:
             // Make sure we're not getting an EXIT event because the user slid
             // off the keyboard area, then force a key press.
-            if (pointInView(x, y) && (key != null)) {
+            if (key != null) {
                 getAccessibilityNodeProvider().simulateKeyPress(key);
             }
             //$FALL-THROUGH$
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index e37868b..e12e5f3 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -37,8 +37,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;
@@ -746,7 +746,7 @@
                     R.styleable.Keyboard_Key);
             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) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 0a70605..127ac71 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -84,6 +84,8 @@
 
     // Miscellaneous constants
     private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
+    private static final float UNDEFINED_RATIO = -1.0f;
+    private static final int UNDEFINED_DIMENSION = -1;
 
     // XML attributes
     protected final float mVerticalCorrection;
@@ -202,7 +204,6 @@
         private final float mKeyHintLetterRatio;
         private final float mKeyShiftedLetterHintRatio;
         private final float mKeyHintLabelRatio;
-        private static final float UNDEFINED_RATIO = -1.0f;
 
         public final Rect mPadding = new Rect();
         public int mKeyLetterSize;
@@ -214,26 +215,22 @@
         public int mKeyHintLabelSize;
         public int mAnimAlpha;
 
-        public KeyDrawParams(TypedArray a) {
+        public KeyDrawParams(final TypedArray a) {
             mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
-            if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
-                mKeyLetterRatio = UNDEFINED_RATIO;
-                mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
-            } else {
-                mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
+            if (!isValidFraction(mKeyLetterRatio = getFraction(a,
+                    R.styleable.KeyboardView_keyLetterSize))) {
+                mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
             }
-            if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
-                mKeyLabelRatio = UNDEFINED_RATIO;
-                mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
-            } else {
-                mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
+            if (!isValidFraction(mKeyLabelRatio = getFraction(a,
+                    R.styleable.KeyboardView_keyLabelSize))) {
+                mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
             }
-            mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
-            mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
-            mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
-            mKeyShiftedLetterHintRatio = getRatio(a,
+            mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
+            mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
+            mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
+            mKeyShiftedLetterHintRatio = getFraction(a,
                     R.styleable.KeyboardView_keyShiftedLetterHintRatio);
-            mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
+            mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
             mKeyLabelHorizontalPadding = a.getDimension(
                     R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
             mKeyHintLetterPadding = a.getDimension(
@@ -260,10 +257,10 @@
         }
 
         public void updateKeyHeight(int keyHeight) {
-            if (mKeyLetterRatio >= 0.0f) {
+            if (isValidFraction(mKeyLetterRatio)) {
                 mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
             }
-            if (mKeyLabelRatio >= 0.0f) {
+            if (isValidFraction(mKeyLabelRatio)) {
                 mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
             }
             mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
@@ -338,7 +335,7 @@
                     R.styleable.KeyboardView_keyPreviewOffset, 0);
             mPreviewHeight = a.getDimensionPixelSize(
                     R.styleable.KeyboardView_keyPreviewHeight, 80);
-            mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
+            mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
             mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
             mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
 
@@ -387,9 +384,24 @@
         mPaint.setAntiAlias(true);
     }
 
-    // Read fraction value in TypedArray as float.
-    /* package */ static float getRatio(TypedArray a, int index) {
-        return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
+    static boolean isValidFraction(final float fraction) {
+        return fraction >= 0.0f;
+    }
+
+    static float getFraction(final TypedArray a, final int index) {
+        final TypedValue value = a.peekValue(index);
+        if (value == null || value.type != TypedValue.TYPE_FRACTION) {
+            return UNDEFINED_RATIO;
+        }
+        return a.getFraction(index, 1, 1, UNDEFINED_RATIO);
+    }
+
+    public static int getDimensionPixelSize(final TypedArray a, final int index) {
+        final TypedValue value = a.peekValue(index);
+        if (value == null || value.type != TypedValue.TYPE_DIMENSION) {
+            return UNDEFINED_DIMENSION;
+        }
+        return a.getDimensionPixelSize(index, UNDEFINED_DIMENSION);
     }
 
     /**
@@ -980,7 +992,7 @@
     }
 
     @Override
-    public void showGestureTrail(PointerTracker tracker) {
+    public void showGesturePreviewTrail(PointerTracker tracker) {
         locatePreviewPlacerView();
         mPreviewPlacerView.invalidatePointer(tracker);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 0cc0b63..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);
 
@@ -344,8 +344,8 @@
                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
         mAutoCorrectionSpacebarLedIcon = a.getDrawable(
                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
-        mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio,
-                1000, 1000, 1) / 1000.0f;
+        mSpacebarTextRatio = a.getFraction(
+                R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
         mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
         mSpacebarTextShadowColor = a.getColor(
                 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index b5b3ef6..be101cf 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -80,7 +80,7 @@
         public void invalidateKey(Key key);
         public void showKeyPreview(PointerTracker tracker);
         public void dismissKeyPreview(PointerTracker tracker);
-        public void showGestureTrail(PointerTracker tracker);
+        public void showGesturePreviewTrail(PointerTracker tracker);
     }
 
     public interface TimerProxy {
@@ -152,8 +152,6 @@
     private static boolean sNeedsPhantomSuddenMoveEventHack;
 
     private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
-    private static final InputPointers sAggregratedPointers = new InputPointers(
-            GestureStroke.DEFAULT_CAPACITY);
     private static PointerTrackerQueue sPointerTrackerQueue;
 
     public final int mPointerId;
@@ -166,13 +164,13 @@
     private Keyboard mKeyboard;
     private int mKeyQuarterWidthSquared;
 
-    private boolean mIsAlphabetKeyboard;
-    private boolean mIsPossibleGesture = false;
-    private boolean mInGesture = false;
-
-    // TODO: Remove these variables
-    private int mLastRecognitionPointSize = 0;
-    private long mLastRecognitionTime = 0;
+    private boolean mIsDetectingGesture = false; // per PointerTracker.
+    private static boolean sInGesture = false;
+    private static long sGestureFirstDownTime;
+    private static final InputPointers sAggregratedPointers = new InputPointers(
+            GestureStroke.DEFAULT_CAPACITY);
+    private static int sLastRecognitionPointSize = 0;
+    private static long sLastRecognitionTime = 0;
 
     // The position and time at which first down event occurred.
     private long mDownTime;
@@ -290,40 +288,6 @@
         }
     }
 
-    // TODO: To handle multi-touch gestures we may want to move this method to
-    // {@link PointerTrackerQueue}.
-    private static InputPointers getIncrementalBatchPoints() {
-        final int trackersSize = sTrackers.size();
-        for (int i = 0; i < trackersSize; ++i) {
-            final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(
-                    sAggregratedPointers);
-        }
-        return sAggregratedPointers;
-    }
-
-    // TODO: To handle multi-touch gestures we may want to move this method to
-    // {@link PointerTrackerQueue}.
-    private static InputPointers getAllBatchPoints() {
-        final int trackersSize = sTrackers.size();
-        for (int i = 0; i < trackersSize; ++i) {
-            final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
-        }
-        return sAggregratedPointers;
-    }
-
-    // TODO: To handle multi-touch gestures we may want to move this method to
-    // {@link PointerTrackerQueue}.
-    public static void clearBatchInputPointsOfAllPointerTrackers() {
-        final int trackersSize = sTrackers.size();
-        for (int i = 0; i < trackersSize; ++i) {
-            final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStrokeWithPreviewTrail.reset();
-        }
-        sAggregratedPointers.reset();
-    }
-
     private PointerTracker(final int id, final KeyEventHandler handler) {
         if (handler == null) {
             throw new NullPointerException();
@@ -338,7 +302,7 @@
 
     // Returns true if keyboard has been changed by this callback.
     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
-        if (mInGesture) {
+        if (sInGesture) {
             return false;
         }
         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -394,7 +358,7 @@
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnRelease(final Key key, final int primaryCode,
             final boolean withSliding) {
-        if (mInGesture) {
+        if (sInGesture) {
             return;
         }
         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -428,7 +392,6 @@
     private void setKeyDetectorInner(final KeyDetector keyDetector) {
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
-        mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
         mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
         final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
         if (newKey != mCurrentKey) {
@@ -502,7 +465,7 @@
             return;
         }
 
-        if (!key.noKeyPreview() && !mInGesture) {
+        if (!key.noKeyPreview() && !sInGesture) {
             mDrawingProxy.showKeyPreview(this);
         }
         updatePressKeyGraphics(key);
@@ -577,50 +540,65 @@
         return newKey;
     }
 
+    private static int getActivePointerTrackerCount() {
+        return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
+    }
+
     private void startBatchInput() {
         if (DEBUG_LISTENER) {
             Log.d(TAG, "onStartBatchInput");
         }
-        mInGesture = true;
+        sInGesture = true;
         mListener.onStartBatchInput();
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
-    private void updateBatchInput(final InputPointers batchPoints) {
-        if (DEBUG_LISTENER) {
-            Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
+    private void updateBatchInput(final long eventTime) {
+        synchronized (sAggregratedPointers) {
+            mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
+            final int size = sAggregratedPointers.getPointerSize();
+            if (size > sLastRecognitionPointSize
+                    && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
+                sLastRecognitionPointSize = size;
+                sLastRecognitionTime = eventTime;
+                if (DEBUG_LISTENER) {
+                    Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+                }
+                mListener.onUpdateBatchInput(sAggregratedPointers);
+            }
         }
-        mListener.onUpdateBatchInput(batchPoints);
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
-    private void endBatchInput(final InputPointers batchPoints) {
-        if (DEBUG_LISTENER) {
-            Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
+    private void endBatchInput() {
+        synchronized (sAggregratedPointers) {
+            mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
+            if (getActivePointerTrackerCount() == 1) {
+                if (DEBUG_LISTENER) {
+                    Log.d(TAG, "onEndBatchInput: batchPoints="
+                            + sAggregratedPointers.getPointerSize());
+                }
+                sInGesture = false;
+                mListener.onEndBatchInput(sAggregratedPointers);
+                clearBatchInputPointsOfAllPointerTrackers();
+            }
         }
-        mListener.onEndBatchInput(batchPoints);
-        clearBatchInputRecognitionStateOfThisPointerTracker();
+        mDrawingProxy.showGesturePreviewTrail(this);
+    }
+
+    private static void abortBatchInput() {
         clearBatchInputPointsOfAllPointerTrackers();
     }
 
-    private void abortBatchInput() {
-        clearBatchInputRecognitionStateOfThisPointerTracker();
-        clearBatchInputPointsOfAllPointerTrackers();
-    }
-
-    private void clearBatchInputRecognitionStateOfThisPointerTracker() {
-        mIsPossibleGesture = false;
-        mInGesture = false;
-        mLastRecognitionPointSize = 0;
-        mLastRecognitionTime = 0;
-    }
-
-    private boolean updateBatchInputRecognitionState(final long eventTime, final int size) {
-        if (size > mLastRecognitionPointSize
-                && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
-            mLastRecognitionPointSize = size;
-            mLastRecognitionTime = eventTime;
-            return true;
+    private static void clearBatchInputPointsOfAllPointerTrackers() {
+        final int trackersSize = sTrackers.size();
+        for (int i = 0; i < trackersSize; ++i) {
+            final PointerTracker tracker = sTrackers.get(i);
+            tracker.mGestureStrokeWithPreviewTrail.reset();
         }
-        return false;
+        sAggregratedPointers.reset();
+        sLastRecognitionPointSize = 0;
+        sLastRecognitionTime = 0;
     }
 
     public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
@@ -671,8 +649,8 @@
             }
         }
 
-        final PointerTrackerQueue queue = sPointerTrackerQueue;
         final Key key = getKeyOn(x, y);
+        final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
             if (key != null && key.isModifier()) {
                 // Before processing a down event of modifier key, all pointers already being
@@ -682,16 +660,26 @@
             queue.add(this);
         }
         onDownEventInternal(x, y, eventTime);
-        if (queue != null && queue.size() == 1) {
-            mIsPossibleGesture = false;
+        if (!sShouldHandleGesture) {
+            return;
+        }
+        final int activePointerTrackerCount = getActivePointerTrackerCount();
+        if (activePointerTrackerCount == 1) {
+            mIsDetectingGesture = false;
             // A gesture should start only from the letter key.
-            if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
-                    && key != null && Keyboard.isLetterCode(key.mCode)) {
-                mIsPossibleGesture = true;
-                // TODO: pointer times should be relative to first down even in entire batch input
-                // instead of resetting to 0 for each new down event.
-                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false);
+            final boolean isAlphabetKeyboard = (mKeyboard != null)
+                    && mKeyboard.mId.isAlphabetKeyboard();
+            if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
+                    && Keyboard.isLetterCode(key.mCode)) {
+                mIsDetectingGesture = true;
+                sGestureFirstDownTime = eventTime;
+                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
             }
+        } else if (sInGesture && activePointerTrackerCount > 1) {
+            mIsDetectingGesture = true;
+            final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
+            mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
+                    false /* isHistorical */);
         }
     }
 
@@ -727,22 +715,18 @@
         mIsInSlidingKeyInput = true;
     }
 
-    private void onGestureMoveEvent(final PointerTracker tracker, final int x, final int y,
-            final long eventTime, final boolean isHistorical, final Key key) {
-        final int gestureTime = (int)(eventTime - tracker.getDownTime());
-        if (sShouldHandleGesture && mIsPossibleGesture) {
+    private void onGestureMoveEvent(final int x, final int y, final long eventTime,
+            final boolean isHistorical, final Key key) {
+        final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
+        if (mIsDetectingGesture) {
             final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
             stroke.addPoint(x, y, gestureTime, isHistorical);
-            if (!mInGesture && stroke.isStartOfAGesture()) {
+            if (!sInGesture && stroke.isStartOfAGesture()) {
                 startBatchInput();
             }
-        }
 
-        if (key != null && mInGesture) {
-            final InputPointers batchPoints = getIncrementalBatchPoints();
-            mDrawingProxy.showGestureTrail(this);
-            if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
-                updateBatchInput(batchPoints);
+            if (sInGesture && key != null) {
+                updateBatchInput(eventTime);
             }
         }
     }
@@ -755,7 +739,7 @@
             return;
         }
 
-        if (me != null) {
+        if (sShouldHandleGesture && me != null) {
             // Add historical points to gesture path.
             final int pointerIndex = me.findPointerIndex(mPointerId);
             final int historicalSize = me.getHistorySize();
@@ -763,24 +747,31 @@
                 final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
                 final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
                 final long historicalTime = me.getHistoricalEventTime(h);
-                onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
+                onGestureMoveEvent(historicalX, historicalY, historicalTime,
                         true /* isHistorical */, null);
             }
         }
 
+        onMoveEventInternal(x, y, eventTime);
+    }
+
+    private void onMoveEventInternal(final int x, final int y, final long eventTime) {
         final int lastX = mLastX;
         final int lastY = mLastY;
         final Key oldKey = mCurrentKey;
         Key key = onMoveKey(x, y);
 
-        // Register move event on gesture tracker.
-        onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
-        if (mInGesture) {
-            mIgnoreModifierKey = true;
-            mTimerProxy.cancelLongPressTimer();
-            mIsInSlidingKeyInput = true;
-            mCurrentKey = null;
-            setReleasedKeyGraphics(oldKey);
+        if (sShouldHandleGesture) {
+            // Register move event on gesture tracker.
+            onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
+            if (sInGesture) {
+                mIgnoreModifierKey = true;
+                mTimerProxy.cancelLongPressTimer();
+                mIsInSlidingKeyInput = true;
+                mCurrentKey = null;
+                setReleasedKeyGraphics(oldKey);
+                return;
+            }
         }
 
         if (key != null) {
@@ -825,7 +816,7 @@
                     // TODO: Should find a way to balance gesture detection and this hack.
                     if (sNeedsPhantomSuddenMoveEventHack
                             && lastMoveSquared >= mKeyQuarterWidthSquared
-                            && !mIsPossibleGesture) {
+                            && !mIsDetectingGesture) {
                         if (DEBUG_MODE) {
                             Log.w(TAG, String.format("onMoveEvent:"
                                     + " phantom sudden move event is translated to "
@@ -843,12 +834,11 @@
                         // touch panels when there are close multiple touches.
                         // Caveat: When in chording input mode with a modifier key, we don't use
                         // this hack.
-                        final PointerTrackerQueue queue = sPointerTrackerQueue;
-                        if (queue != null && queue.size() > 1
-                                && !queue.hasModifierKeyOlderThan(this)) {
+                        if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
+                                && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
                             onUpEventInternal();
                         }
-                        if (!mIsPossibleGesture) {
+                        if (!mIsDetectingGesture) {
                             mKeyAlreadyProcessed = true;
                         }
                         setReleasedKeyGraphics(oldKey);
@@ -866,7 +856,7 @@
                 if (mIsAllowedSlidingKeyInput) {
                     onMoveToNewKey(key, x, y);
                 } else {
-                    if (!mIsPossibleGesture) {
+                    if (!mIsDetectingGesture) {
                         mKeyAlreadyProcessed = true;
                     }
                 }
@@ -881,7 +871,7 @@
 
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
-            if (!mInGesture) {
+            if (!sInGesture) {
                 if (mCurrentKey != null && mCurrentKey.isModifier()) {
                     // Before processing an up event of modifier key, all pointers already being
                     // tracked should be released.
@@ -890,9 +880,11 @@
                     queue.releaseAllPointersOlderThan(this, eventTime);
                 }
             }
-            queue.remove(this);
         }
         onUpEventInternal();
+        if (queue != null) {
+            queue.remove(this);
+        }
     }
 
     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
@@ -910,34 +902,31 @@
     private void onUpEventInternal() {
         mTimerProxy.cancelKeyTimers();
         mIsInSlidingKeyInput = false;
-        mIsPossibleGesture = false;
+        mIsDetectingGesture = false;
+        final Key currentKey = mCurrentKey;
+        mCurrentKey = null;
         // Release the last pressed key.
-        setReleasedKeyGraphics(mCurrentKey);
+        setReleasedKeyGraphics(currentKey);
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
             mIsShowingMoreKeysPanel = false;
         }
 
-        if (mInGesture) {
-            // Register up event on gesture tracker.
-            // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
-            // and/or tapping mode?
-            endBatchInput(getAllBatchPoints());
-            if (mCurrentKey != null) {
-                callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
-                mCurrentKey = null;
+        if (sInGesture) {
+            if (currentKey != null) {
+                callListenerOnRelease(currentKey, currentKey.mCode, true);
             }
-            mDrawingProxy.showGestureTrail(this);
+            endBatchInput();
             return;
         }
-        // This event will be recognized as a regular code input. Clear unused batch points so they
-        // are not mistakenly included in the next batch event.
+        // This event will be recognized as a regular code input. Clear unused possible batch points
+        // so they are not mistakenly displayed as preview.
         clearBatchInputPointsOfAllPointerTrackers();
         if (mKeyAlreadyProcessed) {
             return;
         }
-        if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
-            detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
+        if (currentKey != null && !currentKey.isRepeatable()) {
+            detectAndSendKey(currentKey, mKeyX, mKeyY);
         }
     }
 
@@ -981,7 +970,7 @@
     }
 
     private void startRepeatKey(final Key key) {
-        if (key != null && key.isRepeatable() && !mInGesture) {
+        if (key != null && key.isRepeatable() && !sInGesture) {
             onRegisterKey(key);
             mTimerProxy.startKeyRepeatTimer(this);
         }
@@ -1010,7 +999,7 @@
     }
 
     private void startLongPressTimer(final Key key) {
-        if (key != null && key.isLongPressEnabled() && !mInGesture) {
+        if (key != null && key.isLongPressEnabled() && !sInGesture) {
             mTimerProxy.startLongPressTimer(this);
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 292842d..8251344 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -130,8 +130,12 @@
     }
 
     private void appendBatchPoints(final InputPointers out, final int size) {
+        final int length = size - mLastIncrementalBatchSize;
+        if (length <= 0) {
+            return;
+        }
         out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
-                mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
+                mLastIncrementalBatchSize, length);
         mLastIncrementalBatchSize = size;
     }
 
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/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index 3b08cab..01c281b 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -32,6 +32,9 @@
  * dictionary pack.
  */
 public 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..93a9c88
--- /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 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/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 912f895..50e2204 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -392,16 +392,6 @@
         }
     }
 
-    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 {
         static final int NOT_A_SEPARATOR_CODE_POINT = -1;
 
@@ -451,60 +441,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/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index afc4293..03263d2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -132,7 +132,7 @@
 
     private static class SuggestionStripViewParams {
         private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
-        private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
+        private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
         private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
         private static final int PUNCTUATIONS_IN_STRIP = 5;
 
@@ -196,16 +196,16 @@
                     R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
             mSuggestionStripOption = a.getInt(
                     R.styleable.SuggestionStripView_suggestionStripOption, 0);
-            final float alphaValidTypedWord = getPercent(a,
-                    R.styleable.SuggestionStripView_alphaValidTypedWord, 100);
-            final float alphaTypedWord = getPercent(a,
-                    R.styleable.SuggestionStripView_alphaTypedWord, 100);
-            final float alphaAutoCorrect = getPercent(a,
-                    R.styleable.SuggestionStripView_alphaAutoCorrect, 100);
-            final float alphaSuggested = getPercent(a,
-                    R.styleable.SuggestionStripView_alphaSuggested, 100);
-            mAlphaObsoleted = getPercent(a,
-                    R.styleable.SuggestionStripView_alphaSuggested, 100);
+            final float alphaValidTypedWord = getFraction(a,
+                    R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+            final float alphaTypedWord = getFraction(a,
+                    R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+            final float alphaAutoCorrect = getFraction(a,
+                    R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+            final float alphaSuggested = getFraction(a,
+                    R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+            mAlphaObsoleted = getFraction(a,
+                    R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
             mColorValidTypedWord = applyAlpha(a.getColor(
                     R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
             mColorTypedWord = applyAlpha(a.getColor(
@@ -217,14 +217,14 @@
             mSuggestionsCountInStrip = a.getInt(
                     R.styleable.SuggestionStripView_suggestionsCountInStrip,
                     DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
-            mCenterSuggestionWeight = getPercent(a,
+            mCenterSuggestionWeight = getFraction(a,
                     R.styleable.SuggestionStripView_centerSuggestionPercentile,
                     DEFAULT_CENTER_SUGGESTION_PERCENTILE);
             mMaxMoreSuggestionsRow = a.getInt(
                     R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
                     DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
-            mMinMoreSuggestionsWidth = getRatio(a,
-                    R.styleable.SuggestionStripView_minMoreSuggestionsWidth);
+            mMinMoreSuggestionsWidth = getFraction(a,
+                    R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
             a.recycle();
 
             mMoreSuggestionsHint = getMoreSuggestionsHint(res,
@@ -278,14 +278,8 @@
             return new BitmapDrawable(res, buffer);
         }
 
-        // Read integer value in TypedArray as percent.
-        private static float getPercent(TypedArray a, int index, int defValue) {
-            return a.getInt(index, defValue) / 100.0f;
-        }
-
-        // Read fraction value in TypedArray as float.
-        private static float getRatio(TypedArray a, int index) {
-            return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
+        static float getFraction(final TypedArray a, final int index, final float defValue) {
+            return a.getFraction(index, 1, 1, defValue);
         }
 
         private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {