Merge "add readHeader." into jb-mr1-dev
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 6259725..fbfbb51 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -53,6 +53,8 @@
     <fraction name="key_uppercase_letter_ratio">40%</fraction>
     <fraction name="key_preview_text_ratio">90%</fraction>
     <fraction name="spacebar_text_ratio">40.000%</fraction>
+    <fraction name="key_letter_ratio_5rows">78%</fraction>
+    <fraction name="key_uppercase_letter_ratio_5rows">48%</fraction>
     <dimen name="key_preview_offset">0.0dp</dimen>
 
     <dimen name="key_preview_offset_ics">1.6dp</dimen>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index a478df8..8283cd9 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -51,6 +51,8 @@
     <fraction name="key_hint_label_ratio">34%</fraction>
     <fraction name="key_uppercase_letter_ratio">29%</fraction>
     <fraction name="spacebar_text_ratio">30.0%</fraction>
+    <fraction name="key_letter_ratio_5rows">62%</fraction>
+    <fraction name="key_uppercase_letter_ratio_5rows">36%</fraction>
     <dimen name="key_uppercase_letter_padding">4dp</dimen>
 
     <dimen name="suggestions_strip_padding">252.0dp</dimen>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 5596ba4..78aa605 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -63,6 +63,8 @@
     <fraction name="key_uppercase_letter_ratio">22%</fraction>
     <fraction name="key_preview_text_ratio">50%</fraction>
     <fraction name="spacebar_text_ratio">28.0%</fraction>
+    <fraction name="key_letter_ratio_5rows">52%</fraction>
+    <fraction name="key_uppercase_letter_ratio_5rows">27%</fraction>
     <dimen name="key_preview_height">94.5dp</dimen>
     <dimen name="key_preview_offset">16.0dp</dimen>
 
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index b95c858..ac94c92 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -53,6 +53,8 @@
     <fraction name="key_hint_label_ratio">28%</fraction>
     <fraction name="key_uppercase_letter_ratio">24%</fraction>
     <fraction name="spacebar_text_ratio">24.00%</fraction>
+    <fraction name="key_letter_ratio_5rows">53%</fraction>
+    <fraction name="key_uppercase_letter_ratio_5rows">30%</fraction>
     <dimen name="key_preview_height">107.1dp</dimen>
 
     <dimen name="key_preview_offset_ics">8.0dp</dimen>
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index ce33b73..0fd9ced 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -64,6 +64,8 @@
     <fraction name="key_uppercase_letter_ratio">26%</fraction>
     <fraction name="key_preview_text_ratio">50%</fraction>
     <fraction name="spacebar_text_ratio">29.03%</fraction>
+    <fraction name="key_letter_ratio_5rows">51%</fraction>
+    <fraction name="key_uppercase_letter_ratio_5rows">33%</fraction>
     <dimen name="key_preview_height">94.5dp</dimen>
     <dimen name="key_preview_offset">16.0dp</dimen>
 
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 4fd942b..e0fcd5e 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -65,6 +65,8 @@
     <fraction name="key_uppercase_letter_ratio">35%</fraction>
     <fraction name="key_preview_text_ratio">82%</fraction>
     <fraction name="spacebar_text_ratio">33.735%</fraction>
+    <fraction name="key_letter_ratio_5rows">64%</fraction>
+    <fraction name="key_uppercase_letter_ratio_5rows">41%</fraction>
     <dimen name="key_preview_height">80dp</dimen>
     <dimen name="key_preview_offset">-8.0dp</dimen>
 
diff --git a/java/res/xml-sw600dp-land/kbd_thai.xml b/java/res/xml-sw600dp-land/kbd_thai.xml
index 3143061..a7763f2 100644
--- a/java/res/xml-sw600dp-land/kbd_thai.xml
+++ b/java/res/xml-sw600dp-land/kbd_thai.xml
@@ -23,6 +23,8 @@
     latin:rowHeight="20%p"
     latin:verticalGap="3.20%p"
     latin:keyTypeface="normal"
+    latin:keyLetterSize="@fraction/key_letter_ratio_5rows"
+    latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5rows"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/res/xml-sw600dp/kbd_thai.xml b/java/res/xml-sw600dp/kbd_thai.xml
index 3143061..a7763f2 100644
--- a/java/res/xml-sw600dp/kbd_thai.xml
+++ b/java/res/xml-sw600dp/kbd_thai.xml
@@ -23,6 +23,8 @@
     latin:rowHeight="20%p"
     latin:verticalGap="3.20%p"
     latin:keyTypeface="normal"
+    latin:keyLetterSize="@fraction/key_letter_ratio_5rows"
+    latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5rows"
     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 b7633df..0f8516f 100644
--- a/java/res/xml-sw768dp-land/kbd_thai.xml
+++ b/java/res/xml-sw768dp-land/kbd_thai.xml
@@ -23,6 +23,8 @@
     latin:rowHeight="20%p"
     latin:verticalGap="2.65%p"
     latin:keyTypeface="normal"
+    latin:keyLetterSize="@fraction/key_letter_ratio_5rows"
+    latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5rows"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/res/xml-sw768dp/kbd_thai.xml b/java/res/xml-sw768dp/kbd_thai.xml
index 2be6a25..7e44514 100644
--- a/java/res/xml-sw768dp/kbd_thai.xml
+++ b/java/res/xml-sw768dp/kbd_thai.xml
@@ -23,6 +23,8 @@
     latin:rowHeight="20%p"
     latin:verticalGap="2.95%p"
     latin:keyTypeface="normal"
+    latin:keyLetterSize="@fraction/key_letter_ratio_5rows"
+    latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5rows"
     latin:touchPositionCorrectionData="@null"
 >
     <include
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 178c9ff..d18be4c 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -36,6 +36,7 @@
 import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.StringUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -54,7 +55,6 @@
      * The key code (unicode or custom code) that this key generates.
      */
     public final int mCode;
-    public final int mAltCode;
 
     /** Label to display */
     public final String mLabel;
@@ -89,22 +89,11 @@
 
     /** Icon to display instead of a label. Icon takes precedence over a label */
     private final int mIconId;
-    /** Icon for disabled state */
-    private final int mDisabledIconId;
-    /** Preview version of the icon, for the preview popup */
-    private final int mPreviewIconId;
 
     /** Width of the key, not including the gap */
     public final int mWidth;
     /** Height of the key, not including the gap */
     public final int mHeight;
-    /** The horizontal gap around this key */
-    public final int mHorizontalGap;
-    /** The vertical gap below this key */
-    public final int mVerticalGap;
-    /** The visual insets */
-    public final int mVisualInsetsLeft;
-    public final int mVisualInsetsRight;
     /** X coordinate of the key in the keyboard layout */
     public final int mX;
     /** Y coordinate of the key in the keyboard layout */
@@ -112,8 +101,6 @@
     /** Hit bounding box of the key */
     public final Rect mHitBox = new Rect();
 
-    /** Text to output when pressed. This can be multiple characters, like ".com" */
-    public final CharSequence mOutputText;
     /** More keys */
     public final MoreKeySpec[] mMoreKeys;
     /** More keys column number and flags */
@@ -143,6 +130,32 @@
     private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
     private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
 
+    private final OptionalAttributes mOptionalAttributes;
+
+    private static class OptionalAttributes {
+        /** Text to output when pressed. This can be multiple characters, like ".com" */
+        public final String mOutputText;
+        public final int mAltCode;
+        /** Icon for disabled state */
+        public final int mDisabledIconId;
+        /** Preview version of the icon, for the preview popup */
+        public final int mPreviewIconId;
+        /** The visual insets */
+        public final int mVisualInsetsLeft;
+        public final int mVisualInsetsRight;
+
+        public OptionalAttributes(final String outputText, final int altCode,
+                final int disabledIconId, final int previewIconId,
+                final int visualInsetsLeft, final int visualInsetsRight) {
+            mOutputText = outputText;
+            mAltCode = altCode;
+            mDisabledIconId = disabledIconId;
+            mPreviewIconId = previewIconId;
+            mVisualInsetsLeft = visualInsetsLeft;
+            mVisualInsetsRight = visualInsetsRight;
+        }
+    }
+
     private final int mHashCode;
 
     /** The current pressed state of this key */
@@ -165,10 +178,7 @@
     public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
             int code, String outputText, int x, int y, int width, int height, int labelFlags) {
         mHeight = height - params.mVerticalGap;
-        mHorizontalGap = params.mHorizontalGap;
-        mVerticalGap = params.mVerticalGap;
-        mVisualInsetsLeft = mVisualInsetsRight = 0;
-        mWidth = width - mHorizontalGap;
+        mWidth = width - params.mHorizontalGap;
         mHintLabel = hintLabel;
         mLabelFlags = labelFlags;
         mBackgroundType = BACKGROUND_TYPE_NORMAL;
@@ -176,15 +186,17 @@
         mMoreKeys = null;
         mMoreKeysColumnAndFlags = 0;
         mLabel = label;
-        mOutputText = outputText;
+        if (outputText == null) {
+            mOptionalAttributes = null;
+        } else {
+            mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED,
+                    ICON_UNDEFINED, ICON_UNDEFINED, 0, 0);
+        }
         mCode = code;
         mEnabled = (code != CODE_UNSPECIFIED);
-        mAltCode = CODE_UNSPECIFIED;
         mIconId = iconId;
-        mDisabledIconId = ICON_UNDEFINED;
-        mPreviewIconId = ICON_UNDEFINED;
         // Horizontal gap is divided equally to both sides of the key.
-        mX = x + mHorizontalGap / 2;
+        mX = x + params.mHorizontalGap / 2;
         mY = y;
         mHitBox.set(x, y, x + width + 1, y + height);
 
@@ -205,8 +217,7 @@
             XmlPullParser parser) throws XmlPullParserException {
         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
         final int keyHeight = row.mRowHeight;
-        mVerticalGap = params.mVerticalGap;
-        mHeight = keyHeight - mVerticalGap;
+        mHeight = keyHeight - params.mVerticalGap;
 
         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard_Key);
@@ -220,7 +231,6 @@
         mX = Math.round(keyXPos + horizontalGap / 2);
         mY = keyYPos;
         mWidth = Math.round(keyWidth - horizontalGap);
-        mHorizontalGap = Math.round(horizontalGap);
         mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
                 keyYPos + keyHeight);
         // Update row to have current x coordinate.
@@ -229,15 +239,15 @@
         mBackgroundType = style.getInt(keyAttr,
                 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
 
-        mVisualInsetsLeft = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
+        final int visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
                 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0));
-        mVisualInsetsRight = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
+        final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
                 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0));
         mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIcon));
-        mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+        final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIconDisabled));
-        mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+        final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIconPreview));
 
         mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
@@ -331,11 +341,20 @@
         } else {
             mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
         }
-        mOutputText = outputText;
-        mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale(
+        final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale(
                 KeySpecParser.parseCode(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
                 needsToUpperCase, locale);
+        if (outputText == null && altCode == CODE_UNSPECIFIED
+                && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
+                && visualInsetsLeft == 0 && visualInsetsRight == 0) {
+            mOptionalAttributes = null;
+        } else {
+            mOptionalAttributes = new OptionalAttributes(outputText, altCode,
+                    disabledIconId, previewIconId,
+                    visualInsetsLeft, visualInsetsRight);
+        }
+
         mHashCode = computeHashCode(this);
 
         keyAttr.recycle();
@@ -370,17 +389,17 @@
                 key.mIconId,
                 key.mBackgroundType,
                 Arrays.hashCode(key.mMoreKeys),
-                key.mOutputText,
+                key.getOutputText(),
                 key.mActionFlags,
                 key.mLabelFlags,
                 // Key can be distinguishable without the following members.
-                // key.mAltCode,
-                // key.mDisabledIconId,
-                // key.mPreviewIconId,
+                // key.mOptionalAttributes.mAltCode,
+                // key.mOptionalAttributes.mDisabledIconId,
+                // key.mOptionalAttributes.mPreviewIconId,
                 // key.mHorizontalGap,
                 // key.mVerticalGap,
-                // key.mVisualInsetLeft,
-                // key.mVisualInsetRight,
+                // key.mOptionalAttributes.mVisualInsetLeft,
+                // key.mOptionalAttributes.mVisualInsetRight,
                 // key.mMaxMoreKeysColumn,
         });
     }
@@ -397,7 +416,7 @@
                 && o.mIconId == mIconId
                 && o.mBackgroundType == mBackgroundType
                 && Arrays.equals(o.mMoreKeys, mMoreKeys)
-                && TextUtils.equals(o.mOutputText, mOutputText)
+                && TextUtils.equals(o.getOutputText(), getOutputText())
                 && o.mActionFlags == mActionFlags
                 && o.mLabelFlags == mLabelFlags;
     }
@@ -577,8 +596,20 @@
         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
     }
 
+    public String getOutputText() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs != null) ? attrs.mOutputText : null;
+    }
+
+    public int getAltCode() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
+    }
+
     public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
-        final int iconId = mEnabled ? mIconId : mDisabledIconId;
+        final OptionalAttributes attrs = mOptionalAttributes;
+        final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
+        final int iconId = mEnabled ? mIconId : disabledIconId;
         final Drawable icon = iconSet.getIconDrawable(iconId);
         if (icon != null) {
             icon.setAlpha(alpha);
@@ -587,9 +618,21 @@
     }
 
     public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
-        return mPreviewIconId != ICON_UNDEFINED
-                ? iconSet.getIconDrawable(mPreviewIconId)
-                : iconSet.getIconDrawable(mIconId);
+        final OptionalAttributes attrs = mOptionalAttributes;
+        final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
+        return previewIconId != ICON_UNDEFINED
+                ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
+    }
+
+    public int getDrawX() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft;
+    }
+
+    public int getDrawWidth() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs == null) ? mWidth
+                : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index a5f9e9e..b8e5e95 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -39,6 +39,7 @@
 import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
 import com.android.inputmethod.latin.XmlParseUtils;
 
@@ -123,6 +124,10 @@
 
     /** Per keyboard key visual parameters */
     public final Typeface mKeyTypeface;
+    public final float mKeyLetterRatio;
+    public final int mKeyLetterSize;
+    public final float mKeyHintLetterRatio;
+    public final float mKeyShiftedLetterHintRatio;
 
     public final int mMostCommonKeyHeight;
     public final int mMostCommonKeyWidth;
@@ -155,6 +160,10 @@
         mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
 
         mKeyTypeface = params.mKeyTypeface;
+        mKeyLetterRatio = params.mKeyLetterRatio;
+        mKeyLetterSize = params.mKeyLetterSize;
+        mKeyHintLetterRatio = params.mKeyHintLetterRatio;
+        mKeyShiftedLetterHintRatio = params.mKeyShiftedLetterHintRatio;
 
         mTopPadding = params.mTopPadding;
         mVerticalGap = params.mVerticalGap;
@@ -252,6 +261,10 @@
         public int mHorizontalCenterPadding;
 
         public Typeface mKeyTypeface = null;
+        public float mKeyLetterRatio = ResourceUtils.UNDEFINED_RATIO;
+        public int mKeyLetterSize = ResourceUtils.UNDEFINED_DIMENSION;
+        public float mKeyHintLetterRatio = ResourceUtils.UNDEFINED_RATIO;;
+        public float mKeyShiftedLetterHintRatio = ResourceUtils.UNDEFINED_RATIO;
 
         public int mDefaultRowHeight;
         public int mDefaultKeyWidth;
@@ -383,14 +396,14 @@
         }
 
         private void updateHistogram(Key key) {
-            final int height = key.mHeight + key.mVerticalGap;
+            final int height = key.mHeight + mVerticalGap;
             final int heightCount = updateHistogramCounter(mHeightHistogram, height);
             if (heightCount > mMaxHeightCount) {
                 mMaxHeightCount = heightCount;
                 mMostCommonKeyHeight = height;
             }
 
-            final int width = key.mWidth + key.mHorizontalGap;
+            final int width = key.mWidth + mHorizontalGap;
             final int widthCount = updateHistogramCounter(mWidthHistogram, width);
             if (widthCount > mMaxWidthCount) {
                 mMaxWidthCount = widthCount;
@@ -565,13 +578,13 @@
                 mParams = params;
                 TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                         R.styleable.Keyboard);
-                mRowHeight = (int)Builder.getDimensionOrFraction(keyboardAttr,
+                mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_rowHeight,
                         params.mBaseHeight, params.mDefaultRowHeight);
                 keyboardAttr.recycle();
                 TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                         R.styleable.Keyboard_Key);
-                mDefaultKeyWidth = Builder.getDimensionOrFraction(keyAttr,
+                mDefaultKeyWidth = ResourceUtils.getDimensionOrFraction(keyAttr,
                         R.styleable.Keyboard_Key_keyWidth,
                         params.mBaseWidth, params.mDefaultKeyWidth);
                 mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
@@ -624,7 +637,7 @@
                 final int keyboardRightEdge = mParams.mOccupiedWidth
                         - mParams.mHorizontalEdgesPadding;
                 if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
-                    final float keyXPos = Builder.getDimensionOrFraction(keyAttr,
+                    final float keyXPos = ResourceUtils.getDimensionOrFraction(keyAttr,
                             R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
                     if (keyXPos < 0) {
                         // If keyXPos is negative, the actual x-coordinate will be
@@ -645,7 +658,7 @@
             }
 
             public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
-                final int widthType = Builder.getEnumValue(keyAttr,
+                final int widthType = ResourceUtils.getEnumValue(keyAttr,
                         R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
                 switch (widthType) {
                 case KEYWIDTH_FILL_RIGHT:
@@ -655,7 +668,7 @@
                     // out the area up to the right edge of the keyboard.
                     return keyboardRightEdge - keyXPos;
                 default: // KEYWIDTH_NOT_ENUM
-                    return Builder.getDimensionOrFraction(keyAttr,
+                    return ResourceUtils.getDimensionOrFraction(keyAttr,
                             R.styleable.Keyboard_Key_keyWidth,
                             mParams.mBaseWidth, mDefaultKeyWidth);
                 }
@@ -768,14 +781,14 @@
                     keyboardHeight = keyboardAttr.getDimension(
                             R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
                 }
-                final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
+                final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
-                float minKeyboardHeight = getDimensionOrFraction(keyboardAttr,
+                float minKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
                 if (minKeyboardHeight < 0) {
                     // Specified fraction was negative, so it should be calculated against display
                     // width.
-                    minKeyboardHeight = -getDimensionOrFraction(keyboardAttr,
+                    minKeyboardHeight = -ResourceUtils.getDimensionOrFraction(keyboardAttr,
                             R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
                 }
                 final Params params = mParams;
@@ -784,26 +797,27 @@
                 params.mOccupiedHeight = (int)Math.max(
                         Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
                 params.mOccupiedWidth = params.mId.mWidth;
-                params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
+                params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
-                params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr,
+                params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
-                params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr,
+                params.mHorizontalEdgesPadding = (int)ResourceUtils.getDimensionOrFraction(
+                        keyboardAttr,
                         R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
                         mParams.mOccupiedWidth, 0);
 
                 params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
                         - params.mHorizontalCenterPadding;
-                params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr,
+                params.mDefaultKeyWidth = (int)ResourceUtils.getDimensionOrFraction(keyAttr,
                         R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
                         params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
-                params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr,
+                params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
-                params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr,
+                params.mVerticalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
                 params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
                         - params.mBottomPadding + params.mVerticalGap;
-                params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr,
+                params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_rowHeight, params.mBaseHeight,
                         params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
 
@@ -811,6 +825,14 @@
                     params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt(
                             R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
                 }
+                params.mKeyLetterRatio = ResourceUtils.getFraction(keyboardViewAttr,
+                        R.styleable.KeyboardView_keyLetterSize);
+                params.mKeyLetterSize = ResourceUtils.getDimensionPixelSize(keyboardViewAttr,
+                        R.styleable.KeyboardView_keyLetterSize);
+                params.mKeyHintLetterRatio = ResourceUtils.getFraction(keyboardViewAttr,
+                        R.styleable.KeyboardView_keyHintLetterRatio);
+                params.mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(keyboardViewAttr,
+                        R.styleable.KeyboardView_keyShiftedLetterHintRatio);
 
                 params.mMoreKeysTemplate = keyboardAttr.getResourceId(
                         R.styleable.Keyboard_moreKeysTemplate, 0);
@@ -1221,7 +1243,7 @@
             // If <case> does not have "index" attribute, that means this <case> is wild-card for
             // the attribute.
             return !a.hasValue(index)
-                    || stringArrayContains(a.getString(index).split("\\|"), value);
+                    || StringUtils.containsInArray(value, a.getString(index).split("\\|"));
         }
 
         private static boolean matchTypedValue(TypedArray a, int index, int intValue,
@@ -1232,19 +1254,10 @@
             if (v == null) {
                 return true;
             }
-            if (isIntegerValue(v)) {
+            if (ResourceUtils.isIntegerValue(v)) {
                 return intValue == a.getInt(index, 0);
-            } else if (isStringValue(v)) {
-                return stringArrayContains(a.getString(index).split("\\|"), strValue);
-            }
-            return false;
-        }
-
-        private static boolean stringArrayContains(String[] array, String value) {
-            for (final String elem : array) {
-                if (elem.equals(value)) {
-                    return true;
-                }
+            } else if (ResourceUtils.isStringValue(v)) {
+                return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
             }
             return false;
         }
@@ -1334,47 +1347,6 @@
             mRightEdgeKey = null;
         }
 
-        public static float getDimensionOrFraction(TypedArray a, int index, int base,
-                float defValue) {
-            final TypedValue value = a.peekValue(index);
-            if (value == null) {
-                return defValue;
-            }
-            if (isFractionValue(value)) {
-                return a.getFraction(index, base, base, defValue);
-            } else if (isDimensionValue(value)) {
-                return a.getDimension(index, defValue);
-            }
-            return defValue;
-        }
-
-        public static int getEnumValue(TypedArray a, int index, int defValue) {
-            final TypedValue value = a.peekValue(index);
-            if (value == null) {
-                return defValue;
-            }
-            if (isIntegerValue(value)) {
-                return a.getInt(index, defValue);
-            }
-            return defValue;
-        }
-
-        private static boolean isFractionValue(TypedValue v) {
-            return v.type == TypedValue.TYPE_FRACTION;
-        }
-
-        private static boolean isDimensionValue(TypedValue v) {
-            return v.type == TypedValue.TYPE_DIMENSION;
-        }
-
-        private static boolean isIntegerValue(TypedValue v) {
-            return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
-        }
-
-        private static boolean isStringValue(TypedValue v) {
-            return v.type == TypedValue.TYPE_STRING;
-        }
-
         private static String textAttr(String value, String name) {
             return value != null ? String.format(" %s=%s", name, value) : "";
         }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index a479709..b909a3a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -43,6 +43,7 @@
 import com.android.inputmethod.latin.Constants;
 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.define.ProductionFlag;
@@ -84,8 +85,6 @@
 
     // 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;
@@ -199,6 +198,7 @@
 
         private final Typeface mKeyTypefaceFromKeyboardView;
         private final float mKeyLetterRatio;
+        private final int mKeyLetterSizeFromKeyboardView;
         private final float mKeyLargeLetterRatio;
         private final float mKeyLabelRatio;
         private final float mKeyLargeLabelRatio;
@@ -219,20 +219,22 @@
 
         public KeyDrawParams(final TypedArray a) {
             mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
-            if (!isValidFraction(mKeyLetterRatio = getFraction(a,
-                    R.styleable.KeyboardView_keyLetterSize))) {
-                mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
-            }
-            if (!isValidFraction(mKeyLabelRatio = getFraction(a,
-                    R.styleable.KeyboardView_keyLabelSize))) {
-                mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
-            }
-            mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
-            mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
-            mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
-            mKeyShiftedLetterHintRatio = getFraction(a,
+            mKeyLetterRatio = ResourceUtils.getFraction(a, R.styleable.KeyboardView_keyLetterSize);
+            mKeyLetterSizeFromKeyboardView = ResourceUtils.getDimensionPixelSize(a,
+                    R.styleable.KeyboardView_keyLetterSize);
+            mKeyLabelRatio = ResourceUtils.getFraction(a, R.styleable.KeyboardView_keyLabelSize);
+            mKeyLabelSize = ResourceUtils.getDimensionPixelSize(a,
+                    R.styleable.KeyboardView_keyLabelSize);
+            mKeyLargeLabelRatio = ResourceUtils.getFraction(a,
+                    R.styleable.KeyboardView_keyLargeLabelRatio);
+            mKeyLargeLetterRatio = ResourceUtils.getFraction(a,
+                    R.styleable.KeyboardView_keyLargeLetterRatio);
+            mKeyHintLetterRatio = ResourceUtils.getFraction(a,
+                    R.styleable.KeyboardView_keyHintLetterRatio);
+            mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(a,
                     R.styleable.KeyboardView_keyShiftedLetterHintRatio);
-            mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
+            mKeyHintLabelRatio = ResourceUtils.getFraction(a,
+                    R.styleable.KeyboardView_keyHintLabelRatio);
             mKeyLabelHorizontalPadding = a.getDimension(
                     R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
             mKeyHintLetterPadding = a.getDimension(
@@ -241,19 +243,23 @@
                     R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
             mKeyShiftedLetterHintPadding = a.getDimension(
                     R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
-            mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
+            mKeyTextColor = a.getColor(
+                    R.styleable.KeyboardView_keyTextColor, Color.WHITE);
             mKeyTextInactivatedColor = a.getColor(
-                    R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
-            mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
-            mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
+                    R.styleable.KeyboardView_keyTextInactivatedColor, Color.WHITE);
+            mKeyHintLetterColor = a.getColor(
+                    R.styleable.KeyboardView_keyHintLetterColor, Color.TRANSPARENT);
+            mKeyHintLabelColor = a.getColor(
+                    R.styleable.KeyboardView_keyHintLabelColor, Color.TRANSPARENT);
             mKeyShiftedLetterHintInactivatedColor = a.getColor(
-                    R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
+                    R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor,
+                    Color.TRANSPARENT);
             mKeyShiftedLetterHintActivatedColor = a.getColor(
-                    R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
+                    R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, Color.TRANSPARENT);
             mKeyTypefaceFromKeyboardView = Typeface.defaultFromStyle(
                     a.getInt(R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
             mKeyTypeface = mKeyTypefaceFromKeyboardView;
-            mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
+            mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, Color.TRANSPARENT);
             mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
 
             mKeyBackground.getPadding(mPadding);
@@ -263,19 +269,42 @@
             mKeyTypeface = (keyboard.mKeyTypeface != null)
                     ? keyboard.mKeyTypeface : mKeyTypefaceFromKeyboardView;
             final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
-            if (isValidFraction(mKeyLetterRatio)) {
-                mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
-            }
-            if (isValidFraction(mKeyLabelRatio)) {
+            mKeyLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight,
+                    mKeyLetterSizeFromKeyboardView, mKeyLetterRatio,
+                    mKeyLetterSizeFromKeyboardView);
+            // Override if size/ratio is specified in Keyboard.
+            mKeyLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight, keyboard.mKeyLetterSize,
+                    keyboard.mKeyLetterRatio, mKeyLetterSize);
+            if (ResourceUtils.isValidFraction(mKeyLabelRatio)) {
                 mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
             }
             mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
             mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
-            mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
-            mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio);
+            mKeyHintLetterSize = selectTextSizeFromKeyboardOrView(keyHeight,
+                    keyboard.mKeyHintLetterRatio, mKeyHintLetterRatio);
+            mKeyShiftedLetterHintSize = selectTextSizeFromKeyboardOrView(keyHeight,
+                    keyboard.mKeyShiftedLetterHintRatio, mKeyShiftedLetterHintRatio);
             mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
         }
 
+        private static final int selectTextSizeFromDimensionOrRatio(final int keyHeight,
+                final int dimens, final float ratio, final int defaultDimens) {
+            if (ResourceUtils.isValidDimensionPixelSize(dimens)) {
+                return dimens;
+            }
+            if (ResourceUtils.isValidFraction(ratio)) {
+                return (int)(keyHeight * ratio);
+            }
+            return defaultDimens;
+        }
+
+        private static final int selectTextSizeFromKeyboardOrView(final int keyHeight,
+                final float ratioFromKeyboard, final float ratioFromView) {
+            final float ratio = ResourceUtils.isValidFraction(ratioFromKeyboard)
+                    ? ratioFromKeyboard : ratioFromView;
+            return (int)(keyHeight * ratio);
+        }
+
         public void blendAlpha(final Paint paint) {
             final int color = paint.getColor();
             paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
@@ -341,14 +370,15 @@
                     R.styleable.KeyboardView_keyPreviewOffset, 0);
             mPreviewHeight = a.getDimensionPixelSize(
                     R.styleable.KeyboardView_keyPreviewHeight, 80);
-            mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
+            mPreviewTextRatio = ResourceUtils.getFraction(a,
+                    R.styleable.KeyboardView_keyPreviewTextRatio);
             mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
             mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
         }
 
         public void updateParams(final Keyboard keyboard, final KeyDrawParams keyDrawParams) {
             final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
-            if (isValidFraction(mPreviewTextRatio)) {
+            if (ResourceUtils.isValidFraction(mPreviewTextRatio)) {
                 mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
             }
             mKeyLetterSize = keyDrawParams.mKeyLetterSize;
@@ -387,26 +417,6 @@
         mPaint.setAntiAlias(true);
     }
 
-    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);
-    }
-
     /**
      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
      * view will re-layout itself to accommodate the keyboard.
@@ -598,7 +608,7 @@
     }
 
     private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
-        final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
+        final int keyDrawX = key.getDrawX() + getPaddingLeft();
         final int keyDrawY = key.mY + getPaddingTop();
         canvas.translate(keyDrawX, keyDrawY);
 
@@ -613,8 +623,7 @@
 
     // Draw key background.
     protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
-        final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
-                + params.mPadding.left + params.mPadding.right;
+        final int bgWidth = key.getDrawWidth() + params.mPadding.left + params.mPadding.right;
         final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
         final int bgX = -params.mPadding.left;
         final int bgY = -params.mPadding.top;
@@ -635,7 +644,7 @@
 
     // Draw key top visuals.
     protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
-        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.mHeight;
         final float centerX = keyWidth * 0.5f;
         final float centerY = keyHeight * 0.5f;
@@ -811,7 +820,7 @@
 
     // Draw popup hint "..." at the bottom right corner of the key.
     protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
-        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.mHeight;
 
         paint.setTypeface(params.mKeyTypeface);
@@ -1002,7 +1011,11 @@
     @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
     @Override
     public void showKeyPreview(PointerTracker tracker) {
-        if (!mShowKeyPreviewPopup) return;
+        final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
+        if (!mShowKeyPreviewPopup) {
+            params.mPreviewVisibleOffset = -mKeyboard.mVerticalGap;
+            return;
+        }
 
         final TextView previewText = getKeyPreviewText(tracker.mPointerId);
         // If the key preview has no parent view yet, add it to the ViewGroup which can place
@@ -1019,7 +1032,6 @@
         if (key == null)
             return;
 
-        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 onDraw().
         if (label != null) {
@@ -1042,7 +1054,7 @@
 
         previewText.measure(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyDrawWidth = key.getDrawWidth();
         final int previewWidth = previewText.getMeasuredWidth();
         final int previewHeight = params.mPreviewHeight;
         // The width and height of visible part of the key preview background. The content marker
@@ -1058,8 +1070,7 @@
         // The key preview is horizontally aligned with the center of the visible part of the
         // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
         // the left/right background is used if such background is specified.
-        int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2
-                + params.mCoordinates[0];
+        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0];
         if (previewX < 0) {
             previewX = 0;
             if (params.mPreviewLeftBackground != null) {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 358061b..e8e6c15 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -334,7 +334,7 @@
                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
         final Resources res = getResources();
         final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
-               ResourceUtils.getDeviceOverrideValue(res,
+                ResourceUtils.getDeviceOverrideValue(res,
                         R.array.phantom_sudden_move_event_device_list, "false"));
         PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack);
 
@@ -618,9 +618,9 @@
         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
         // aligned with the bottom edge of the visible part of the key preview.
-        final int pointY = parentKey.mY + (keyPreviewEnabled
-                ? mKeyPreviewDrawParams.mPreviewVisibleOffset
-                : -parentKey.mVerticalGap);
+        // {@code mPreviewVisibleOffset} has been set appropriately in
+        // {@link KeyboardView#showKeyPreview(PointerTracker)}.
+        final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
         moreKeysPanel.showMoreKeysPanel(
                 this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
         final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index be101cf..5a79d50 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -330,10 +330,10 @@
             final int y) {
         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
         final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
-        final int code = altersCode ? key.mAltCode : primaryCode;
+        final int code = altersCode ? key.getAltCode() : primaryCode;
         if (DEBUG_LISTENER) {
-            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
-                    + " x=" + x + " y=" + y
+            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code)
+                    + " text=" + key.getOutputText() + " x=" + x + " y=" + y
                     + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
                     + " enabled=" + key.isEnabled());
         }
@@ -347,7 +347,7 @@
         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
         if (key.isEnabled() || altersCode) {
             if (code == Keyboard.CODE_OUTPUT_TEXT) {
-                mListener.onTextInput(key.mOutputText);
+                mListener.onTextInput(key.getOutputText());
             } else if (code != Keyboard.CODE_UNSPECIFIED) {
                 mListener.onCodeInput(code, x, y);
             }
@@ -440,13 +440,13 @@
         }
 
         if (key.altCodeWhileTyping()) {
-            final int altCode = key.mAltCode;
+            final int altCode = key.getAltCode();
             final Key altKey = mKeyboard.getKey(altCode);
             if (altKey != null) {
                 updateReleaseKeyGraphics(altKey);
             }
             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
-                if (k != key && k.mAltCode == altCode) {
+                if (k != key && k.getAltCode() == altCode) {
                     updateReleaseKeyGraphics(k);
                 }
             }
@@ -479,13 +479,13 @@
         }
 
         if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
-            final int altCode = key.mAltCode;
+            final int altCode = key.getAltCode();
             final Key altKey = mKeyboard.getKey(altCode);
             if (altKey != null) {
                 updatePressKeyGraphics(altKey);
             }
             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
-                if (k != key && k.mAltCode == altCode) {
+                if (k != key && k.getAltCode() == altCode) {
                     updatePressKeyGraphics(k);
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
index e01ac3d..5021ad3 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -17,11 +17,16 @@
 package com.android.inputmethod.latin;
 
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.Build;
+import android.util.TypedValue;
 
 import java.util.HashMap;
 
 public final class ResourceUtils {
+    public static final float UNDEFINED_RATIO = -1.0f;
+    public static final int UNDEFINED_DIMENSION = -1;
+
     private ResourceUtils() {
         // This utility class is not publicly instantiable.
     }
@@ -45,4 +50,79 @@
         }
         return sDeviceOverrideValueMap.get(key);
     }
+
+    public static boolean isValidFraction(final float fraction) {
+        return fraction >= 0.0f;
+    }
+
+    // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size.
+    public static boolean isValidDimensionPixelSize(final int dimension) {
+        return dimension > 0;
+    }
+
+    // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset.
+    public static boolean isValidDimensionPixelOffset(final int dimension) {
+        return dimension >= 0;
+    }
+
+    public static float getFraction(final TypedArray a, final int index, final float defValue) {
+        final TypedValue value = a.peekValue(index);
+        if (value == null || !isFractionValue(value)) {
+            return defValue;
+        }
+        return a.getFraction(index, 1, 1, defValue);
+    }
+
+    public static float getFraction(final TypedArray a, final int index) {
+        return getFraction(a, index, UNDEFINED_RATIO);
+    }
+
+    public static int getDimensionPixelSize(final TypedArray a, final int index) {
+        final TypedValue value = a.peekValue(index);
+        if (value == null || !isDimensionValue(value)) {
+            return ResourceUtils.UNDEFINED_DIMENSION;
+        }
+        return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION);
+    }
+
+    public static float getDimensionOrFraction(TypedArray a, int index, int base,
+            float defValue) {
+        final TypedValue value = a.peekValue(index);
+        if (value == null) {
+            return defValue;
+        }
+        if (isFractionValue(value)) {
+            return a.getFraction(index, base, base, defValue);
+        } else if (isDimensionValue(value)) {
+            return a.getDimension(index, defValue);
+        }
+        return defValue;
+    }
+
+    public static int getEnumValue(TypedArray a, int index, int defValue) {
+        final TypedValue value = a.peekValue(index);
+        if (value == null) {
+            return defValue;
+        }
+        if (isIntegerValue(value)) {
+            return a.getInt(index, defValue);
+        }
+        return defValue;
+    }
+
+    public static boolean isFractionValue(TypedValue v) {
+        return v.type == TypedValue.TYPE_FRACTION;
+    }
+
+    public static boolean isDimensionValue(TypedValue v) {
+        return v.type == TypedValue.TYPE_DIMENSION;
+    }
+
+    public static boolean isIntegerValue(TypedValue v) {
+        return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
+    }
+
+    public static boolean isStringValue(TypedValue v) {
+        return v.type == TypedValue.TYPE_STRING;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 03263d2..9e8ab81 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -61,6 +61,7 @@
 import com.android.inputmethod.latin.CollectionUtils;
 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.SuggestedWords;
 import com.android.inputmethod.latin.Utils;
@@ -196,15 +197,15 @@
                     R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
             mSuggestionStripOption = a.getInt(
                     R.styleable.SuggestionStripView_suggestionStripOption, 0);
-            final float alphaValidTypedWord = getFraction(a,
+            final float alphaValidTypedWord = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
-            final float alphaTypedWord = getFraction(a,
+            final float alphaTypedWord = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
-            final float alphaAutoCorrect = getFraction(a,
+            final float alphaAutoCorrect = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
-            final float alphaSuggested = getFraction(a,
+            final float alphaSuggested = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
-            mAlphaObsoleted = getFraction(a,
+            mAlphaObsoleted = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
             mColorValidTypedWord = applyAlpha(a.getColor(
                     R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
@@ -217,13 +218,13 @@
             mSuggestionsCountInStrip = a.getInt(
                     R.styleable.SuggestionStripView_suggestionsCountInStrip,
                     DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
-            mCenterSuggestionWeight = getFraction(a,
+            mCenterSuggestionWeight = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_centerSuggestionPercentile,
                     DEFAULT_CENTER_SUGGESTION_PERCENTILE);
             mMaxMoreSuggestionsRow = a.getInt(
                     R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
                     DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
-            mMinMoreSuggestionsWidth = getFraction(a,
+            mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
                     R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
             a.recycle();
 
@@ -278,10 +279,6 @@
             return new BitmapDrawable(res, buffer);
         }
 
-        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) {
             final CharSequence word = suggestedWords.getWord(pos);
             final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 71a6d6a..cd9ff85 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -257,7 +257,7 @@
                     for (Key keyboardKey : keyboardKeys) {
                         mJsonWriter.beginObject();
                         mJsonWriter.name("code").value(keyboardKey.mCode);
-                        mJsonWriter.name("altCode").value(keyboardKey.mAltCode);
+                        mJsonWriter.name("altCode").value(keyboardKey.getAltCode());
                         mJsonWriter.name("x").value(keyboardKey.mX);
                         mJsonWriter.name("y").value(keyboardKey.mY);
                         mJsonWriter.name("w").value(keyboardKey.mWidth);
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 918fcf5..9bb81a0 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1045,7 +1045,7 @@
             final int y, final boolean ignoreModifierKey, final boolean altersCode,
             final int code) {
         if (key != null) {
-            CharSequence outputText = key.mOutputText;
+            String outputText = key.getOutputText();
             final Object[] values = {
                 Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null
                         : scrubDigitsFromString(outputText.toString()),
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index e681f6f..765632e 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -302,6 +302,6 @@
     if (keyId0 >= 0 && keyId1 >= 0) {
         return mKeyKeyDistancesG[keyId0][keyId1];
     }
-    return 0;
+    return MAX_POINT_TO_KEY_LENGTH;
 }
 } // namespace latinime